This is an automated email from the ASF dual-hosted git repository. tn pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tooling-atr-experiments.git
The following commit(s) were added to refs/heads/main by this push: new b08a2a3 add api blueprint, integrate quart-schema to generate an openapi definition for the api blueprint b08a2a3 is described below commit b08a2a3073bc4fb050efc220a357b746c57fed1f Author: Thomas Neidhart <t...@apache.org> AuthorDate: Mon Feb 17 22:21:51 2025 +0100 add api blueprint, integrate quart-schema to generate an openapi definition for the api blueprint --- atr/blueprints/__init__.py | 2 +- atr/blueprints/{ => api}/__init__.py | 16 ++------------ atr/blueprints/{__init__.py => api/api.py} | 22 +++++++++---------- atr/server.py | 17 +++++++++++++++ atr/templates/pages.html | 14 ++++++++++++ poetry.lock | 35 +++++++++++++++++++++++++++++- pyproject.toml | 3 ++- uv.lock | 24 ++++++++++++++++++++ 8 files changed, 105 insertions(+), 28 deletions(-) diff --git a/atr/blueprints/__init__.py b/atr/blueprints/__init__.py index d5ccb09..4d8d1cf 100644 --- a/atr/blueprints/__init__.py +++ b/atr/blueprints/__init__.py @@ -20,7 +20,7 @@ from importlib.util import find_spec from asfquart.base import QuartApp -_BLUEPRINT_MODULES = ["secret"] +_BLUEPRINT_MODULES = ["api", "secret"] def register_blueprints(app: QuartApp) -> None: diff --git a/atr/blueprints/__init__.py b/atr/blueprints/api/__init__.py similarity index 62% copy from atr/blueprints/__init__.py copy to atr/blueprints/api/__init__.py index d5ccb09..9831705 100644 --- a/atr/blueprints/__init__.py +++ b/atr/blueprints/api/__init__.py @@ -15,18 +15,6 @@ # specific language governing permissions and limitations # under the License. -from importlib import import_module -from importlib.util import find_spec +from quart import Blueprint -from asfquart.base import QuartApp - -_BLUEPRINT_MODULES = ["secret"] - - -def register_blueprints(app: QuartApp) -> None: - for routes_name in _BLUEPRINT_MODULES: - routes_fqn = f"atr.blueprints.{routes_name}.{routes_name}" - spec = find_spec(routes_fqn) - if spec is not None: - module = import_module(routes_fqn) - app.register_blueprint(module.blueprint) +blueprint = Blueprint("api_blueprint", __name__, url_prefix="/api") diff --git a/atr/blueprints/__init__.py b/atr/blueprints/api/api.py similarity index 62% copy from atr/blueprints/__init__.py copy to atr/blueprints/api/api.py index d5ccb09..0dac16f 100644 --- a/atr/blueprints/__init__.py +++ b/atr/blueprints/api/api.py @@ -15,18 +15,18 @@ # specific language governing permissions and limitations # under the License. -from importlib import import_module -from importlib.util import find_spec +from collections.abc import Mapping +from typing import Any -from asfquart.base import QuartApp +from atr.db.service import get_pmc_by_name -_BLUEPRINT_MODULES = ["secret"] +from . import blueprint -def register_blueprints(app: QuartApp) -> None: - for routes_name in _BLUEPRINT_MODULES: - routes_fqn = f"atr.blueprints.{routes_name}.{routes_name}" - spec = find_spec(routes_fqn) - if spec is not None: - module = import_module(routes_fqn) - app.register_blueprint(module.blueprint) +@blueprint.route("/pmc/<project_name>") +async def api_pmc(project_name: str) -> tuple[Mapping[str, Any], int]: + pmc = await get_pmc_by_name(project_name) + if pmc: + return pmc.model_dump(), 200 + else: + return {}, 404 diff --git a/atr/server.py b/atr/server.py index 738a12b..2203d78 100644 --- a/atr/server.py +++ b/atr/server.py @@ -19,8 +19,11 @@ import logging import os +from collections.abc import Iterable from decouple import config +from quart_schema import OpenAPIProvider, QuartSchema +from werkzeug.routing import Rule import asfquart import asfquart.generics @@ -35,6 +38,13 @@ asfquart.generics.OAUTH_URL_INIT = "https://oauth.apache.org/auth?state=%s&redir asfquart.generics.OAUTH_URL_CALLBACK = "https://oauth.apache.org/token?code=%s" +class ApiOnlyOpenAPIProvider(OpenAPIProvider): + def generate_rules(self) -> Iterable[Rule]: + for rule in super().generate_rules(): + if rule.rule.startswith("/api"): + yield rule + + def register_routes() -> str: from . import routes @@ -48,6 +58,13 @@ def create_app(app_config: type[AppConfig]) -> QuartApp: app = asfquart.construct(__name__) app.config.from_object(app_config) + QuartSchema( + app, + openapi_provider_class=ApiOnlyOpenAPIProvider, + swagger_ui_path="/api/docs", + openapi_path="/api/openapi.json", + ) + # # Configure static folder path before changing working directory # app.static_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") diff --git a/atr/templates/pages.html b/atr/templates/pages.html index 4fc6127..13eee25 100644 --- a/atr/templates/pages.html +++ b/atr/templates/pages.html @@ -221,6 +221,20 @@ </div> </div> </div> + + <div class="endpoint-group"> + <h2>API</h2> + + <div class="endpoint"> + <h3> + <a href="{{ url_for('swagger_ui') }}">/api/docs</a> + </h3> + <div class="endpoint-description">Swagger UI.</div> + <div class="endpoint-meta"> + Access: <span class="access-requirement public">Public</span> + </div> + </div> + </div> </div> {% endblock content %} diff --git a/poetry.lock b/poetry.lock index 163468d..934169a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1831,6 +1831,18 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyhumps" +version = "3.8.0" +description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6"}, + {file = "pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3"}, +] + [[package]] name = "pyright" version = "1.1.394" @@ -2043,6 +2055,27 @@ werkzeug = ">=3.0" [package.extras] dotenv = ["python-dotenv"] +[[package]] +name = "quart-schema" +version = "0.21.0" +description = "A Quart extension to provide schema validation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "quart_schema-0.21.0-py3-none-any.whl", hash = "sha256:57b8f7d2f8cc5bcbf6b2200ed17f870fc26811c5d0ac5c6a02352644ab068129"}, + {file = "quart_schema-0.21.0.tar.gz", hash = "sha256:5edcd18adb223c9d0f234688238882884fb09a4ee52fb29b50928821adbb7064"}, +] + +[package.dependencies] +pyhumps = ">=1.6.1" +quart = ">=0.19.0" + +[package.extras] +docs = ["pydata_sphinx_theme", "sphinx-tabs (>=3.4.4)"] +msgspec = ["msgspec (>=0.18)"] +pydantic = ["pydantic (>=2)"] + [[package]] name = "regex" version = "2024.11.6" @@ -2647,4 +2680,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.1" python-versions = "~=3.13" -content-hash = "b0037bd47d793570a6513cf1b5d6303920af3aa7470c3a79525fb5ab1ad133d6" +content-hash = "b8552c1164755503fcb5154d109acb0798abc7d5c1220a478ea3017d86be198f" diff --git a/pyproject.toml b/pyproject.toml index 95cb593..1161586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,8 @@ dependencies = [ "hypercorn~=0.17", "python-gnupg~=0.5", "sqlmodel~=0.0", - "python-decouple~=3.8" + "python-decouple~=3.8", + "quart-schema~=0.21" ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 2bfd4f6..6794dc9 100644 --- a/uv.lock +++ b/uv.lock @@ -867,6 +867,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, ] +[[package]] +name = "pyhumps" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/83/fa6f8fb7accb21f39e8f2b6a18f76f6d90626bdb0a5e5448e5cc9b8ab014/pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3", size = 9018 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/11/a1938340ecb32d71e47ad4914843775011e6e9da59ba1229f181fef3119e/pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6", size = 6095 }, +] + [[package]] name = "pyright" version = "1.1.394" @@ -988,6 +997,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/e9/cc28f21f52913adf333f653b9e0a3bf9cb223f5083a26422968ba73edd8d/quart-0.20.0-py3-none-any.whl", hash = "sha256:003c08f551746710acb757de49d9b768986fd431517d0eb127380b656b98b8f1", size = 77960 }, ] +[[package]] +name = "quart-schema" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyhumps" }, + { name = "quart" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/d6/6ba58252b660cd2ff98f0758e46ce5aa3f39976c2db61dfcfa80883c570a/quart_schema-0.21.0.tar.gz", hash = "sha256:5edcd18adb223c9d0f234688238882884fb09a4ee52fb29b50928821adbb7064", size = 23733 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/b7/c5cfa45ec6835005f72c7d667508e91a5f00f656ece2bd83e9ecb0d8643d/quart_schema-0.21.0-py3-none-any.whl", hash = "sha256:57b8f7d2f8cc5bcbf6b2200ed17f870fc26811c5d0ac5c6a02352644ab068129", size = 21360 }, +] + [[package]] name = "regex" version = "2024.11.6" @@ -1118,6 +1140,7 @@ dependencies = [ { name = "hypercorn" }, { name = "python-decouple" }, { name = "python-gnupg" }, + { name = "quart-schema" }, { name = "sqlmodel" }, ] @@ -1143,6 +1166,7 @@ requires-dist = [ { name = "hypercorn", specifier = "~=0.17" }, { name = "python-decouple", specifier = "~=3.8" }, { name = "python-gnupg", specifier = "~=0.5" }, + { name = "quart-schema", specifier = "~=0.21" }, { name = "sqlmodel", specifier = "~=0.0" }, ] --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tooling.apache.org For additional commands, e-mail: dev-h...@tooling.apache.org