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 <[email protected]>
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)
[email protected]("/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: [email protected]
For additional commands, e-mail: [email protected]