phlo-api 0.2.3__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phlo_api-0.2.3 → phlo_api-0.3.0}/PKG-INFO +1 -1
- {phlo_api-0.2.3 → phlo_api-0.3.0}/README.md +9 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/pyproject.toml +1 -1
- phlo_api-0.3.0/src/phlo_api/__init__.py +22 -0
- phlo_api-0.3.0/src/phlo_api/api/__init__.py +20 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/authentication.py +134 -6
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/authorization.py +348 -77
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/maintenance.py +61 -2
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/observability.py +194 -9
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/main.py +293 -15
- phlo_api-0.3.0/src/phlo_api/observatory_api/__init__.py +25 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/contributing.py +53 -2
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/dagster.py +522 -57
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/extension_settings.py +59 -3
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/extensions.py +62 -4
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/iceberg.py +72 -16
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/lineage.py +91 -6
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/loki.py +62 -6
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/nessie.py +52 -3
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/quality.py +61 -7
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/search.py +29 -4
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/settings.py +51 -3
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/trino.py +254 -20
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/trino_sql.py +105 -7
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2.py +2367 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_actions.py +133 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_capabilities.py +325 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_catalog.py +81 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_governance.py +10 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_metadata.py +62 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_models.py +496 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_observability.py +89 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_products.py +15 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_runs.py +108 -0
- phlo_api-0.3.0/src/phlo_api/observatory_api/v2_storage.py +83 -0
- phlo_api-0.3.0/src/phlo_api/plugin.py +76 -0
- phlo_api-0.3.0/src/phlo_api/regulated_surface_adapter.py +173 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/service.yaml +8 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/PKG-INFO +1 -1
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/SOURCES.txt +22 -0
- phlo_api-0.3.0/tests/test_authentication_api.py +151 -0
- phlo_api-0.3.0/tests/test_authorization_api.py +387 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_integration_api.py +172 -22
- phlo_api-0.3.0/tests/test_loki_api.py +59 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_observability_api.py +83 -0
- phlo_api-0.3.0/tests/test_observatory_v2_actions.py +34 -0
- phlo_api-0.3.0/tests/test_observatory_v2_api.py +954 -0
- phlo_api-0.3.0/tests/test_observatory_v2_capabilities.py +290 -0
- phlo_api-0.3.0/tests/test_observatory_v2_catalog.py +111 -0
- phlo_api-0.3.0/tests/test_observatory_v2_observability.py +105 -0
- phlo_api-0.3.0/tests/test_observatory_v2_storage.py +112 -0
- phlo_api-0.3.0/tests/test_observatory_v2_surfaces.py +67 -0
- phlo_api-0.3.0/tests/test_service_correlation_runtime.py +62 -0
- phlo_api-0.2.3/src/phlo_api/__init__.py +0 -0
- phlo_api-0.2.3/src/phlo_api/api/__init__.py +0 -1
- phlo_api-0.2.3/src/phlo_api/observatory_api/__init__.py +0 -6
- phlo_api-0.2.3/src/phlo_api/plugin.py +0 -31
- phlo_api-0.2.3/tests/test_authorization_api.py +0 -113
- {phlo_api-0.2.3 → phlo_api-0.3.0}/setup.cfg +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/Dockerfile +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/dependency_links.txt +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/entry_points.txt +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/requires.txt +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/top_level.txt +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_api_backend_endpoints.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_api_import_boundaries.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_lineage_api.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_maintenance_api.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_rest_api.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_trino_defaults.py +0 -0
- {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_trino_sql.py +0 -0
|
@@ -20,6 +20,15 @@ phlo plugin install api
|
|
|
20
20
|
| --------------- | --------- | --------------- |
|
|
21
21
|
| `PHLO_API_PORT` | `4000` | API server port |
|
|
22
22
|
| `HOST` | `0.0.0.0` | API server host |
|
|
23
|
+
| `PHLO_AUTHORIZATION_BACKEND` | unset | Authorization backend capability name |
|
|
24
|
+
| `PHLO_AUTHORIZATION_MODE` | `optional` | Guard behavior when no authorization backend exists |
|
|
25
|
+
|
|
26
|
+
With `PHLO_AUTHORIZATION_MODE=optional`, guarded routes remain reachable until an
|
|
27
|
+
authorization backend is configured. Set `PHLO_AUTHORIZATION_MODE=required` to
|
|
28
|
+
fail closed with HTTP `503` on guarded routes when no backend is available.
|
|
29
|
+
|
|
30
|
+
You can also declare these settings in `phlo.yaml` via `api.authorization` or
|
|
31
|
+
`services.phlo-api.authorization`.
|
|
23
32
|
|
|
24
33
|
## Auto-Configuration
|
|
25
34
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Phlo REST API package.
|
|
2
|
+
|
|
3
|
+
This package provides the FastAPI-based REST API service for Phlo,
|
|
4
|
+
exposing platform internals to the Observatory web application.
|
|
5
|
+
|
|
6
|
+
Modules:
|
|
7
|
+
main: FastAPI application and core endpoints.
|
|
8
|
+
plugin: Service plugin registration for phlo-api.
|
|
9
|
+
api: Platform API routers (authentication, authorization, maintenance, observability).
|
|
10
|
+
observatory_api: Observatory-specific API routers (Dagster, Trino, Nessie, etc.).
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
To start the API server locally:
|
|
14
|
+
|
|
15
|
+
.. code-block:: python
|
|
16
|
+
|
|
17
|
+
from phlo_api.main import app
|
|
18
|
+
import uvicorn
|
|
19
|
+
|
|
20
|
+
uvicorn.run(app, host="0.0.0.0", port=4000)
|
|
21
|
+
|
|
22
|
+
"""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Platform API routers exposed by phlo-api.
|
|
2
|
+
|
|
3
|
+
This package contains core platform API routers for authentication,
|
|
4
|
+
authorization, maintenance, and observability operations.
|
|
5
|
+
|
|
6
|
+
Modules:
|
|
7
|
+
authentication: Authentication provider integration for FastAPI.
|
|
8
|
+
authorization: Authorization and permission checking.
|
|
9
|
+
maintenance: Iceberg maintenance observability endpoints.
|
|
10
|
+
observability: Platform health and metrics endpoints.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
Routers from this package are automatically registered by main.py:
|
|
14
|
+
|
|
15
|
+
.. code-block:: python
|
|
16
|
+
|
|
17
|
+
from phlo_api.api.maintenance import router as maintenance_router
|
|
18
|
+
app.include_router(maintenance_router, prefix="/api/maintenance")
|
|
19
|
+
|
|
20
|
+
"""
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
"""Authentication helpers for phlo-api.
|
|
2
2
|
|
|
3
3
|
This module provides authentication capability integration for FastAPI routes.
|
|
4
|
+
It bridges the phlo capabilities system with FastAPI request handling, enabling
|
|
5
|
+
flexible authentication through pluggable providers.
|
|
6
|
+
|
|
7
|
+
Key Functions:
|
|
8
|
+
get_authentication_provider: Resolve the configured authentication provider.
|
|
9
|
+
require_authentication_provider: Resolve provider or raise if unavailable.
|
|
10
|
+
authenticate_request: Authenticate a FastAPI request.
|
|
11
|
+
get_request_principal: Get the authenticated principal from a request.
|
|
12
|
+
require_principal: Get principal or raise HTTP 401.
|
|
13
|
+
|
|
14
|
+
Environment Variables:
|
|
15
|
+
PHLO_AUTHENTICATION_PROVIDER: Name of the authentication provider to use.
|
|
16
|
+
Required when multiple providers are installed.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
Using authentication in a FastAPI route:
|
|
20
|
+
|
|
21
|
+
.. code-block:: python
|
|
22
|
+
|
|
23
|
+
from fastapi import Request
|
|
24
|
+
from phlo_api.api.authentication import require_principal
|
|
25
|
+
|
|
26
|
+
@app.get("/protected")
|
|
27
|
+
async def protected_route(request: Request):
|
|
28
|
+
principal = require_principal(request)
|
|
29
|
+
return {"user": principal.subject}
|
|
30
|
+
|
|
4
31
|
"""
|
|
5
32
|
|
|
6
33
|
from __future__ import annotations
|
|
@@ -18,6 +45,7 @@ from phlo.capabilities import (
|
|
|
18
45
|
list_capabilities,
|
|
19
46
|
resolve_capability,
|
|
20
47
|
)
|
|
48
|
+
from phlo.infrastructure.config import get_authentication_provider_config
|
|
21
49
|
from phlo.logging import get_logger
|
|
22
50
|
|
|
23
51
|
logger = get_logger(__name__)
|
|
@@ -25,13 +53,34 @@ logger = get_logger(__name__)
|
|
|
25
53
|
_AUTHENTICATION_PROVIDER_ENV = "PHLO_AUTHENTICATION_PROVIDER"
|
|
26
54
|
|
|
27
55
|
|
|
56
|
+
def _configured_authentication_provider_name() -> str | None:
|
|
57
|
+
"""Resolve the provider name from env first, then phlo.yaml."""
|
|
58
|
+
provider_name = os.environ.get(_AUTHENTICATION_PROVIDER_ENV)
|
|
59
|
+
if provider_name is not None:
|
|
60
|
+
normalized = provider_name.strip()
|
|
61
|
+
return normalized or None
|
|
62
|
+
|
|
63
|
+
return get_authentication_provider_config()
|
|
64
|
+
|
|
65
|
+
|
|
28
66
|
def get_authentication_provider() -> AuthenticationProvider | None:
|
|
29
67
|
"""Resolve the authentication provider capability.
|
|
30
68
|
|
|
31
|
-
Returns None if no provider is configured.
|
|
32
|
-
|
|
69
|
+
Returns None if no provider is configured. Raises when multiple providers
|
|
70
|
+
are installed without explicit selection.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
None: No arguments required.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
AuthenticationProvider instance, or None if not configured.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
RuntimeError: If the configured provider is not registered, or if multiple
|
|
80
|
+
providers are available without explicit selection.
|
|
81
|
+
|
|
33
82
|
"""
|
|
34
|
-
provider_name =
|
|
83
|
+
provider_name = _configured_authentication_provider_name()
|
|
35
84
|
result = resolve_capability("authentication_provider", provider_name)
|
|
36
85
|
if provider_name and result is None:
|
|
37
86
|
raise RuntimeError(
|
|
@@ -55,7 +104,18 @@ def get_authentication_provider() -> AuthenticationProvider | None:
|
|
|
55
104
|
|
|
56
105
|
|
|
57
106
|
def require_authentication_provider() -> AuthenticationProvider:
|
|
58
|
-
"""Resolve the authentication provider or raise if not available.
|
|
107
|
+
"""Resolve the authentication provider or raise if not available.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
None: No arguments required.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
AuthenticationProvider instance.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
RuntimeError: If no authentication provider is configured.
|
|
117
|
+
|
|
118
|
+
"""
|
|
59
119
|
provider = get_authentication_provider()
|
|
60
120
|
if provider is None:
|
|
61
121
|
raise RuntimeError("Authentication provider not configured")
|
|
@@ -63,7 +123,21 @@ def require_authentication_provider() -> AuthenticationProvider:
|
|
|
63
123
|
|
|
64
124
|
|
|
65
125
|
def create_request_context(request: Request) -> RequestContext:
|
|
66
|
-
"""Create a RequestContext from a FastAPI request.
|
|
126
|
+
"""Create a RequestContext from a FastAPI request.
|
|
127
|
+
|
|
128
|
+
Extracts headers, cookies, query params, and connection metadata from
|
|
129
|
+
the incoming request for use by authentication providers.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
request: The FastAPI request object.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
RequestContext populated with request metadata.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
None: No exceptions raised directly.
|
|
139
|
+
|
|
140
|
+
"""
|
|
67
141
|
headers_dict: dict[str, str] = {}
|
|
68
142
|
for key, value in request.headers.items():
|
|
69
143
|
headers_dict[key.lower()] = value
|
|
@@ -89,6 +163,16 @@ def authenticate_request(request: Request) -> AuthResult:
|
|
|
89
163
|
|
|
90
164
|
Returns an AuthResult that indicates whether authentication succeeded.
|
|
91
165
|
Caches the result in request.state to avoid duplicate authentication.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
request: The FastAPI request object.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
AuthResult with authentication status and principal (if successful).
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
None: No exceptions raised directly.
|
|
175
|
+
|
|
92
176
|
"""
|
|
93
177
|
if hasattr(request.state, _AUTH_RESULT_CACHE_KEY):
|
|
94
178
|
return request.state[_AUTH_RESULT_CACHE_KEY]
|
|
@@ -113,6 +197,16 @@ def get_request_principal(request: Request) -> AuthPrincipal | None:
|
|
|
113
197
|
|
|
114
198
|
Returns None if no authentication provider is configured or authentication failed.
|
|
115
199
|
Caches the result in request.state to avoid duplicate authentication.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
request: The FastAPI request object.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
AuthPrincipal if authenticated, None otherwise.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
None: No exceptions raised directly.
|
|
209
|
+
|
|
116
210
|
"""
|
|
117
211
|
if hasattr(request.state, _AUTH_PRINCIPAL_CACHE_KEY):
|
|
118
212
|
return request.state[_AUTH_PRINCIPAL_CACHE_KEY]
|
|
@@ -133,6 +227,16 @@ def require_principal(request: Request) -> AuthPrincipal:
|
|
|
133
227
|
|
|
134
228
|
Raises HTTPException 401 if authentication failed or no provider is configured.
|
|
135
229
|
Uses cached result from request.state if available.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
request: The FastAPI request object.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
AuthPrincipal if authentication succeeds.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
HTTPException: 401 if authentication fails or provider unavailable.
|
|
239
|
+
|
|
136
240
|
"""
|
|
137
241
|
if hasattr(request.state, _AUTH_PRINCIPAL_CACHE_KEY):
|
|
138
242
|
cached = request.state[_AUTH_PRINCIPAL_CACHE_KEY]
|
|
@@ -185,6 +289,16 @@ def optional_authenticate(request: Request) -> AuthPrincipal | None:
|
|
|
185
289
|
|
|
186
290
|
Unlike require_principal, this does not raise on authentication failure.
|
|
187
291
|
Useful for routes that have different behavior for authenticated vs anonymous.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
request: The FastAPI request object.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
AuthPrincipal if authentication succeeds, None otherwise.
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
None: No exceptions raised directly.
|
|
301
|
+
|
|
188
302
|
"""
|
|
189
303
|
provider = get_authentication_provider()
|
|
190
304
|
if provider is None:
|
|
@@ -200,7 +314,21 @@ def optional_authenticate(request: Request) -> AuthPrincipal | None:
|
|
|
200
314
|
|
|
201
315
|
|
|
202
316
|
def get_capabilities_metadata() -> dict[str, Any]:
|
|
203
|
-
"""Get metadata about available authentication capabilities.
|
|
317
|
+
"""Get metadata about available authentication capabilities.
|
|
318
|
+
|
|
319
|
+
Returns a dictionary with available providers, current provider setting,
|
|
320
|
+
and environment variable information.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
None: No arguments required.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Dictionary with authentication capability metadata.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
None: No exceptions raised directly.
|
|
330
|
+
|
|
331
|
+
"""
|
|
204
332
|
available_providers = list_capabilities("authentication_provider")
|
|
205
333
|
current_provider = os.environ.get(_AUTHENTICATION_PROVIDER_ENV)
|
|
206
334
|
|