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.
Files changed (71) hide show
  1. {phlo_api-0.2.3 → phlo_api-0.3.0}/PKG-INFO +1 -1
  2. {phlo_api-0.2.3 → phlo_api-0.3.0}/README.md +9 -0
  3. {phlo_api-0.2.3 → phlo_api-0.3.0}/pyproject.toml +1 -1
  4. phlo_api-0.3.0/src/phlo_api/__init__.py +22 -0
  5. phlo_api-0.3.0/src/phlo_api/api/__init__.py +20 -0
  6. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/authentication.py +134 -6
  7. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/authorization.py +348 -77
  8. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/maintenance.py +61 -2
  9. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/api/observability.py +194 -9
  10. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/main.py +293 -15
  11. phlo_api-0.3.0/src/phlo_api/observatory_api/__init__.py +25 -0
  12. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/contributing.py +53 -2
  13. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/dagster.py +522 -57
  14. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/extension_settings.py +59 -3
  15. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/extensions.py +62 -4
  16. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/iceberg.py +72 -16
  17. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/lineage.py +91 -6
  18. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/loki.py +62 -6
  19. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/nessie.py +52 -3
  20. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/quality.py +61 -7
  21. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/search.py +29 -4
  22. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/settings.py +51 -3
  23. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/trino.py +254 -20
  24. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/observatory_api/trino_sql.py +105 -7
  25. phlo_api-0.3.0/src/phlo_api/observatory_api/v2.py +2367 -0
  26. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_actions.py +133 -0
  27. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_capabilities.py +325 -0
  28. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_catalog.py +81 -0
  29. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_governance.py +10 -0
  30. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_metadata.py +62 -0
  31. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_models.py +496 -0
  32. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_observability.py +89 -0
  33. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_products.py +15 -0
  34. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_runs.py +108 -0
  35. phlo_api-0.3.0/src/phlo_api/observatory_api/v2_storage.py +83 -0
  36. phlo_api-0.3.0/src/phlo_api/plugin.py +76 -0
  37. phlo_api-0.3.0/src/phlo_api/regulated_surface_adapter.py +173 -0
  38. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/service.yaml +8 -0
  39. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/PKG-INFO +1 -1
  40. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/SOURCES.txt +22 -0
  41. phlo_api-0.3.0/tests/test_authentication_api.py +151 -0
  42. phlo_api-0.3.0/tests/test_authorization_api.py +387 -0
  43. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_integration_api.py +172 -22
  44. phlo_api-0.3.0/tests/test_loki_api.py +59 -0
  45. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_observability_api.py +83 -0
  46. phlo_api-0.3.0/tests/test_observatory_v2_actions.py +34 -0
  47. phlo_api-0.3.0/tests/test_observatory_v2_api.py +954 -0
  48. phlo_api-0.3.0/tests/test_observatory_v2_capabilities.py +290 -0
  49. phlo_api-0.3.0/tests/test_observatory_v2_catalog.py +111 -0
  50. phlo_api-0.3.0/tests/test_observatory_v2_observability.py +105 -0
  51. phlo_api-0.3.0/tests/test_observatory_v2_storage.py +112 -0
  52. phlo_api-0.3.0/tests/test_observatory_v2_surfaces.py +67 -0
  53. phlo_api-0.3.0/tests/test_service_correlation_runtime.py +62 -0
  54. phlo_api-0.2.3/src/phlo_api/__init__.py +0 -0
  55. phlo_api-0.2.3/src/phlo_api/api/__init__.py +0 -1
  56. phlo_api-0.2.3/src/phlo_api/observatory_api/__init__.py +0 -6
  57. phlo_api-0.2.3/src/phlo_api/plugin.py +0 -31
  58. phlo_api-0.2.3/tests/test_authorization_api.py +0 -113
  59. {phlo_api-0.2.3 → phlo_api-0.3.0}/setup.cfg +0 -0
  60. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api/Dockerfile +0 -0
  61. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/dependency_links.txt +0 -0
  62. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/entry_points.txt +0 -0
  63. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/requires.txt +0 -0
  64. {phlo_api-0.2.3 → phlo_api-0.3.0}/src/phlo_api.egg-info/top_level.txt +0 -0
  65. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_api_backend_endpoints.py +0 -0
  66. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_api_import_boundaries.py +0 -0
  67. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_lineage_api.py +0 -0
  68. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_maintenance_api.py +0 -0
  69. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_rest_api.py +0 -0
  70. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_trino_defaults.py +0 -0
  71. {phlo_api-0.2.3 → phlo_api-0.3.0}/tests/test_trino_sql.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phlo-api
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Phlo API - Backend service exposing Phlo internals to Observatory
5
5
  Author-email: Phlo Team <team@phlo.dev>
6
6
  License: MIT
@@ -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
 
@@ -19,7 +19,7 @@ dependencies = [
19
19
  description = "Phlo API - Backend service exposing Phlo internals to Observatory"
20
20
  name = "phlo-api"
21
21
  requires-python = ">=3.11"
22
- version = "0.2.3"
22
+ version = "0.3.0"
23
23
 
24
24
  [[project.authors]]
25
25
  email = "team@phlo.dev"
@@ -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
- Raises when multiple providers are installed without explicit selection.
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 = os.environ.get(_AUTHENTICATION_PROVIDER_ENV)
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