eoapi-cdk 11.4.1 → 11.6.0

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 (46) hide show
  1. package/.jsii +38 -23
  2. package/README.md +2 -0
  3. package/lib/bastion-host/index.js +1 -1
  4. package/lib/database/index.js +1 -1
  5. package/lib/ingestor-api/index.d.ts +4 -0
  6. package/lib/ingestor-api/index.js +8 -3
  7. package/lib/ingestor-api/runtime/Dockerfile +13 -9
  8. package/lib/ingestor-api/runtime/pyproject.toml +37 -0
  9. package/lib/ingestor-api/runtime/src/config.py +9 -0
  10. package/lib/ingestor-api/runtime/src/handler.py +27 -4
  11. package/lib/ingestor-api/runtime/src/ingestor.py +1 -1
  12. package/lib/ingestor-api/runtime/src/schemas.py +11 -19
  13. package/lib/ingestor-api/runtime/src/services.py +4 -2
  14. package/lib/ingestor-api/runtime/uv.lock +1313 -0
  15. package/lib/lambda-api-gateway/index.js +1 -1
  16. package/lib/lambda-api-gateway-private/index.js +1 -1
  17. package/lib/stac-api/index.js +2 -2
  18. package/lib/stac-api/runtime/README.md +36 -0
  19. package/lib/stac-api/runtime/pyproject.toml +2 -2
  20. package/lib/stac-api/runtime/src/stac_api/handler.py +82 -86
  21. package/lib/stac-api/runtime/uv.lock +206 -182
  22. package/lib/stac-auth-proxy/index.js +2 -2
  23. package/lib/stac-auth-proxy/runtime/pyproject.toml +2 -2
  24. package/lib/stac-auth-proxy/runtime/src/stac_auth_proxy_api/handler.py +25 -2
  25. package/lib/stac-auth-proxy/runtime/uv.lock +174 -248
  26. package/lib/stac-browser/index.js +1 -1
  27. package/lib/stac-loader/index.js +2 -2
  28. package/lib/stac-loader/runtime/Dockerfile +1 -0
  29. package/lib/stac-loader/runtime/src/stac_loader/handler.py +2 -25
  30. package/lib/stac-loader/runtime/uv.lock +251 -252
  31. package/lib/stactools-item-generator/index.js +1 -1
  32. package/lib/stactools-item-generator/runtime/uv.lock +79 -75
  33. package/lib/tipg-api/index.js +2 -2
  34. package/lib/tipg-api/runtime/pyproject.toml +1 -1
  35. package/lib/tipg-api/runtime/src/tipg_api/handler.py +85 -75
  36. package/lib/tipg-api/runtime/uv.lock +248 -243
  37. package/lib/titiler-pgstac-api/index.js +2 -2
  38. package/lib/titiler-pgstac-api/runtime/pyproject.toml +2 -2
  39. package/lib/titiler-pgstac-api/runtime/src/titiler_pgstac_api/handler.py +79 -50
  40. package/lib/titiler-pgstac-api/runtime/uv.lock +319 -280
  41. package/lib/utils/utils.py +49 -15
  42. package/package.json +4 -4
  43. package/pyproject.toml +4 -0
  44. package/uv.lock +475 -73
  45. package/lib/ingestor-api/runtime/dev_requirements.txt +0 -4
  46. package/lib/ingestor-api/runtime/requirements.txt +0 -10
@@ -1,9 +1,10 @@
1
- """
2
- Handler for AWS Lambda.
3
- """
1
+ """Handler for AWS Lambda."""
4
2
 
5
- import asyncio
3
+ import logging
6
4
  import os
5
+ from collections.abc import AsyncIterator, Mapping
6
+ from contextlib import asynccontextmanager
7
+ from typing import Any
7
8
 
8
9
  from mangum import Mangum
9
10
  from snapshot_restore_py import register_after_restore, register_before_snapshot
@@ -15,12 +16,15 @@ from tipg.settings import (
15
16
  DatabaseSettings,
16
17
  PostgresSettings,
17
18
  )
18
- from utils import get_secret_dict
19
+ from utils import ensure_event_loop, get_secret_dict, run_async
20
+
21
+ logger = logging.getLogger(__name__)
19
22
 
20
23
  db_settings = DatabaseSettings()
21
24
  custom_sql_settings = CustomSQLSettings()
22
25
 
23
26
  _connection_initialized = False
27
+ _original_lifespan = app.router.lifespan_context
24
28
 
25
29
 
26
30
  def _build_postgres_settings() -> PostgresSettings:
@@ -35,95 +39,101 @@ def _build_postgres_settings() -> PostgresSettings:
35
39
  )
36
40
 
37
41
 
38
- @register_before_snapshot
39
- def on_snapshot():
40
- """
41
- Runtime hook called by Lambda before taking a snapshot.
42
- We close database connections that shouldn't be in the snapshot.
43
- """
44
-
45
- # Close any existing database connections before the snapshot is taken
42
+ def _close_db_pool() -> None:
43
+ """Close the current database pool if one exists."""
46
44
  if hasattr(app, "state") and hasattr(app.state, "pool") and app.state.pool:
47
45
  try:
48
46
  app.state.pool.close()
47
+ except Exception:
48
+ logger.exception("SnapStart: error closing database pool")
49
+ finally:
49
50
  app.state.pool = None
50
- except Exception as e:
51
- print(f"SnapStart: Error closing database pool: {e}")
52
-
53
- return {"statusCode": 200}
54
51
 
55
52
 
56
- @register_after_restore
57
- def on_snap_restore():
58
- """
59
- Runtime hook called by Lambda after restoring from a snapshot.
60
- We recreate database connections that were closed before the snapshot.
61
- """
53
+ async def _initialize_connection() -> None:
54
+ """Create a fresh database connection pool and register collections."""
62
55
  global _connection_initialized
63
56
 
64
- try:
65
- # Get the event loop or create a new one
66
- try:
67
- loop = asyncio.get_running_loop()
68
- except RuntimeError:
69
- loop = asyncio.new_event_loop()
70
- asyncio.set_event_loop(loop)
71
-
72
- # Close any existing pool (from snapshot)
73
- if hasattr(app.state, "pool") and app.state.pool:
74
- try:
75
- app.state.pool.close()
76
- except Exception as e:
77
- print(f"SnapStart: Error closing stale pool: {e}")
78
- app.state.pool = None
79
-
80
- # Create fresh connection pool
81
- postgres_settings = _build_postgres_settings()
82
- loop.run_until_complete(
83
- connect_to_db(
84
- app,
85
- schemas=db_settings.schemas,
86
- tipg_schema=db_settings.tipg_schema,
87
- user_sql_files=custom_sql_settings.sql_files,
88
- settings=postgres_settings,
89
- )
90
- )
91
-
92
- loop.run_until_complete(
93
- register_collection_catalog(
94
- app,
95
- db_settings=db_settings,
96
- )
97
- )
98
-
99
- _connection_initialized = True
100
-
101
- except Exception as e:
102
- print(f"SnapStart: Failed to initialize database connection: {e}")
103
- raise
104
-
105
- return {"statusCode": 200}
106
-
107
-
108
- @app.on_event("startup")
109
- async def startup_event() -> None:
110
- """Connect to database on startup."""
111
- postgres_settings = _build_postgres_settings()
57
+ _close_db_pool()
112
58
  await connect_to_db(
113
59
  app,
114
60
  schemas=db_settings.schemas,
115
61
  tipg_schema=db_settings.tipg_schema,
116
62
  user_sql_files=custom_sql_settings.sql_files,
117
- settings=postgres_settings,
63
+ settings=_build_postgres_settings(),
118
64
  )
119
65
  await register_collection_catalog(
120
66
  app,
121
67
  db_settings=db_settings,
122
68
  )
69
+ _connection_initialized = True
70
+
71
+
72
+ async def _shutdown_connection() -> None:
73
+ """Close the current database pool if it exists."""
74
+ global _connection_initialized
75
+
76
+ _close_db_pool()
77
+ _connection_initialized = False
123
78
 
124
79
 
125
- handler = Mangum(app, lifespan="off")
80
+ @register_before_snapshot
81
+ def on_snapshot() -> dict[str, int]:
82
+ """Close database connections before Lambda SnapStart takes a snapshot."""
83
+ _close_db_pool()
84
+ return {"statusCode": 200}
85
+
86
+
87
+ @register_after_restore
88
+ def on_snap_restore() -> dict[str, int]:
89
+ """Recreate database connections after Lambda SnapStart restores a snapshot."""
90
+ try:
91
+ run_async(_initialize_connection())
92
+ except Exception:
93
+ logger.exception("SnapStart: failed to initialize database connection")
94
+ raise
95
+
96
+ return {"statusCode": 200}
97
+
98
+
99
+ @asynccontextmanager
100
+ async def lifespan(app_instance) -> AsyncIterator[Mapping[str, Any] | None]:
101
+ """Wrap the upstream lifespan with database setup and teardown.
102
+
103
+ We keep the app's lifespan wiring intact for non-Lambda contexts, but the
104
+ Lambda runtime below uses ``Mangum(..., lifespan="off")`` and performs
105
+ connection setup explicitly. In sandbox testing, Mangum lifespan handling
106
+ was not a drop-in replacement for Lambda-container-scoped pool reuse.
107
+ """
108
+ async with _original_lifespan(app_instance) as state:
109
+ await _initialize_connection()
110
+ try:
111
+ yield state
112
+ finally:
113
+ await _shutdown_connection()
114
+
115
+
116
+ app.router.lifespan_context = lifespan
117
+
118
+ # The Lambda runtime initializes long-lived async resources on an installed
119
+ # reusable loop, then hands request execution to Mangum. ``ensure_event_loop``
120
+ # is a defensive step for those synchronous initialization paths. It is not here
121
+ # because normal FastAPI route execution cannot run without it.
122
+ _asgi_handler = Mangum(app, lifespan="off")
123
+
124
+
125
+ def handler(event: Any, context: Any) -> dict[str, Any]:
126
+ """Handle AWS Lambda events with a reusable installed event loop.
127
+
128
+ This supports synchronous Lambda-side async setup such as cold-start and
129
+ SnapStart restore initialization before control passes to Mangum.
130
+ """
131
+ ensure_event_loop()
132
+ return _asgi_handler(event, context)
133
+
126
134
 
127
135
  if "AWS_EXECUTION_ENV" in os.environ:
128
- loop = asyncio.get_event_loop()
129
- loop.run_until_complete(app.router.startup())
136
+ # Avoid ``asyncio.run(...)`` here. It would create the pool on a temporary
137
+ # loop and then close it, which is a poor fit for container-scoped async
138
+ # resources that should live on the installed reusable loop.
139
+ run_async(_initialize_connection())