-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
frontend: OpenAPI first steps #2473
Conversation
After our last discussion about API documentation, I was looking forward to experimenting with all the available packages. apiflaskIn this PR, I wanted to show https://apiflask.com/ You can access the pretty docs page at http://127.0.0.1:5000/docs and you can download the API spec at http://127.0.0.1:5000/openapi.json This is just a proof-of-concept, if you prefer any other API package, we can throw this away. |
flask-restxI also tried https://flask-restx.readthedocs.io/en/latest/index.html which looked the best at first sight, but it seems to really be only for REST APIs. I.e. it expects routes to be classes that implement flask-openapi3The https://luolingchun.github.io/flask-openapi3/ looks good but it seems to have less ways how to do things. For example, query parameters need to be defined as classes https://luolingchun.github.io/flask-openapi3/Tutorial/Request/#query . The flasggerI haven't tried https://github.com/flasgger/flasgger yet. It seems to be really straightforward - maybe too much. |
Seems like flasgger has noticeable more stars on GH. @jpopelka don't you remember why you've chosen flask-restyx for the Packit project? |
Just for the record, I'm not a big fan of the marshmallow models (too many fixes needed over the years..). |
If we don't like marshmallow schemas, then I think we have two options
FTR, even the apiflask that I use in this PR AFAIK internally uses marshmallow. I think these are marshmallow from apiflask import Schema
from apiflask.fields import Integer, String, List, Nested So we need to decide what exactly we don't like about marshmallow and how much we don't like it :D |
Writing them is easy, but I got a feeling it has an unstable API. The Initially I thought we could live with something like python's built-in BTW. the marshmallow in Fedora is maintained by us, and I was so glad we |
I think I just searched for a library to generate the API documentation (Swagger), found an article about (now unmaintained) Flask-RestPlus (from which Flask-RESTX was later forked due to inactivity), tried it, it did what we needed, done.
yup, but the difference is IMHO not so big
|
bd3788c
to
b070135
Compare
Or the other way around, depends on what we happen to define at the moment.
56baf3a
to
a837590
Compare
Finally on a review, sorry for the delay. This looks very nice, thank you! Some general notes to the
I'd prefer to have our main documentation pointing at the API documentation directly. Perhaps at least in the |
# > Initializing the Api object always registers the root endpoint / even if | ||
# > the Swagger UI path is changed. If you wish to use the root endpoint / | ||
# > for other purposes, you must register it before initializing the Api object. | ||
# TODO That needs to get fixed and then we can move this route to apiv3_general |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's never a good time to do this. Can we have an issue for this (we should create a new api end point at least, and twist our library to use it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I linked the issue in the code comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should create a new api end point at least, and twist our library to use it
I am not sure if I understand here. Just to clarify, there is no regression - I needed to move the @apiv3_ns.route("/")
route to a different file than it was before but it works as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we want to change/break this long term, right? Then we need some plan somewhere.
|
||
# Workaround - `marshal_with` needs the input `build_id` to be present | ||
# in the returned dict. Don't worry, it won't get to the end user, it | ||
# will be stripped away. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you have some restx or other reference link?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I couldn't find any. This traceback happens when you remove the workaround:
werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'apiv3_ns.build_get_build' with values ['chroots', 'ended_on', 'id', 'is_background', 'ownername', 'project_dirname', 'projectname', 'repo_url', 'source_package', 'started_on', 'state', 'submitted_on', 'submitter']. Did you forget to specify values ['build_id']?
It doesn't make any sense to me, but adding the build_id
key fixed the traceback. Of course, the build_id
doesn't appear in the result because it is not defined in the schema, so it gets filtered out.
""" | ||
Unless this is an error on my side, there is a bug in flask-restx displaying | ||
all URL path parameters inside the Swagger documentation as as required. | ||
This is a temporary hack until it gets fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has this been reported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it turned out, it was an error on my side. Updated.
|
||
def edit_package_parser(): | ||
# pylint: disable=missing-function-docstring | ||
parser = add_package_parser().copy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a deep copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say, yes.
parser_copy.args = deepcopy(self.args)
https://flask-restx.readthedocs.io/en/latest/_modules/flask_restx/reqparse.html#RequestParser.copy
def post(self, ownername, projectname, package_name, source_type_text=None): | ||
""" | ||
Edit a package | ||
Edit an existing package within a Copr project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicate doc line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was kind of intentional.
The first docstring line is used for the "card" header and therefore it is visible even when the route is collapsed. The rest of the docstring is displayed once the route is expanded.
So I basically wanted to write the docstring like
"""
Edit a package
Edit a package within a Copr project.
"""
but there is a bug in the docstring parsing - python-restx/flask-restx#536
Anyway, we can write the docstrings in any format you want. My preference would be at least a two-line docstring so that we have at least some text when the route is expanded.
) | ||
|
||
# TODO We are copy-pasting descriptions from web UI to this file. This field | ||
# is an ideal candidate for figuring out how to share the descriptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/me nods
|
||
package_builds_schema = { | ||
"latest": Nested(build_model, allow_null=True), | ||
"latest_succeeded": Nested(build_model, allow_null=True), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We miss "builds" field here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the with_all_builds
option is exposed to the API
Your branch is up to date with 'origin/main'
$ git grep with_all_builds frontend/
frontend/coprs_frontend/coprs/models.py: def to_dict(self, with_latest_build=False, with_latest_succeeded_build=False, with_all_builds=False):
frontend/coprs_frontend/coprs/models.py: if with_all_builds:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed
copr --debug get-package --name python-backoff --with-all-builds @copr/copr-dev
# Debug log enabled #
[10:48:13] {/usr/lib/python3.11/site-packages/urllib3/connectionpool.py:228} DEBUG - Starting new HTTP connection (1): localhost:55555
[10:48:13] {/usr/lib/python3.11/site-packages/urllib3/connectionpool.py:456} DEBUG - http://localhost:55555 "GET /api_3/package?ownername=%40copr&projectname=copr-dev&packagename=python-backoff&with_latest_build=False&with_latest_succeeded_build=False HTTP/1.1" 200 458
[10:48:13] {/usr/lib/python3.11/site-packages/urllib3/connectionpool.py:228} DEBUG - Starting new HTTP connection (1): localhost:55555
[10:48:13] {/usr/lib/python3.11/site-packages/urllib3/connectionpool.py:456} DEBUG - http://localhost:55555 "GET /api_3/build/list?ownername=%40copr&projectname=copr-dev&packagename=python-backoff HTTP/1.1" 200 809
It is a pity we have poor coverage for these routes; so I'm a bit afraid that we'll miss some of the fields or so. But it is worth the risk I'd say! I like the change. @jpopelka would you mind taking a 5-minute look? No big review, just check whether the steps we do here are meaningful, or if there isn't an obvious mistake somewhere. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks OK to me 👍
I don't think so, the favicon path seems to be "hardcoded" Packit uses the default favicon as well
Sure
Let's do this in a separate PR, we want to show the documentation but we also want to show users how to generate their API keys. |
Thank you very much for taking a look
Same here, but at least in the future we won't have to be afraid of regressions like missing fields. |
I added some changes. Since it is a big PR, I kept them as separate commits for easier review. I will rebase and squash them before merging. |
+1, let's rebase and merge - so we can continue on making it even more awesome |
See fedora-copr#1865 This is still miles from being finished. I tried probably all OpenAPI libraries that are available and decided to go with flask-restx. It is already in Fedora, Packit team uses it as well, and it is malleable enough to be comfortable used within our codebase. I intentionally defined all data types and descriptions in `schema.py` and in the way that everything is defined separately in its own variable. We should IMHO try to compose things as much as possible to avoid copy-pasting. Remains to be done: - Only a fraction of API endpoints are migrated under flask-restx, we need to finish the rest of them - There is a potential to generate WTForms forms or SQLAlchemy models from the API schema or the other way around. - We should share descriptions between API fields and web UI as much as possible - Packit team uses does something like this, we should do as well @koji_builds_ns.response(HTTPStatus.OK, "OK, koji build group details follow") Error responses should be documented the same way.
See #1865