modmex-lambda 0.1.0__tar.gz → 0.2.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.
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/.github/workflows/ci.yml +1 -0
- modmex_lambda-0.1.0/README.md → modmex_lambda-0.2.0/PKG-INFO +87 -3
- modmex_lambda-0.1.0/PKG-INFO → modmex_lambda-0.2.0/README.md +72 -16
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/__init__.py +12 -1
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/__init__.py +7 -1
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/api_gateway.py +3 -0
- modmex_lambda-0.2.0/modmex_lambda/event_handler/dependencies/__init__.py +13 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/dependant.py +4 -1
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/depends.py +85 -5
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/routing.py +1 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/types.py +2 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/poetry.lock +18 -1
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/pyproject.toml +2 -1
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_api_gateway.py +120 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_dependencies.py +47 -0
- modmex_lambda-0.1.0/tests/event_handler/__init__.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/.github/workflows/release.yml +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/.gitignore +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/LICENSE +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/__init__.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/api_gateway_authorizer_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/api_gateway_websocket_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/common.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/constants.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/content_types.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/cors.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/compat.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/dependency_middleware.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/params.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/types.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/exception_handler.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/exceptions.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/gateway_response.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/middlewares.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/params.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/request.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/response.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/routing_fallbacks.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_sources.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/exceptions.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/logging.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/params.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/parser.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/request.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/resolver.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/response.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/routing.py +0 -0
- {modmex_lambda-0.1.0/modmex_lambda/event_handler/dependencies → modmex_lambda-0.2.0/modmex_lambda/shared}/__init__.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/shared/cookies.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/shared/headers_serializer.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/shared/json_encoder.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/shared/types.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/validation.py +0 -0
- {modmex_lambda-0.1.0/modmex_lambda/shared → modmex_lambda-0.2.0/tests}/__init__.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/conftest.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/data_classes/test_api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/data_classes/test_cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/data_classes/test_common.py +0 -0
- {modmex_lambda-0.1.0/tests → modmex_lambda-0.2.0/tests/event_handler}/__init__.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_cors.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_exception_handler.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_gateway_response.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_request.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_response.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/event_handler/test_routing.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/shared/test_cookies_headers.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/shared/test_json_encoder.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/test_logging.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/test_parser_event_sources.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/test_reexports.py +0 -0
- {modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/test_validation.py +0 -0
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modmex-lambda
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling.
|
|
5
|
+
Author: Modmex
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: modmex<2.0.0,>=1.1.10
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
12
|
+
Provides-Extra: injector
|
|
13
|
+
Requires-Dist: injector<1.0.0,>=0.24.0; extra == 'injector'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
1
16
|
# modmex-lambda
|
|
2
17
|
|
|
3
18
|
Ultra-lightweight AWS Lambda utilities for API Gateway-first workloads.
|
|
@@ -13,11 +28,17 @@ logging with a small dependency footprint.
|
|
|
13
28
|
pip install modmex-lambda
|
|
14
29
|
```
|
|
15
30
|
|
|
16
|
-
|
|
31
|
+
|
|
32
|
+
To use the optional `injector` integration:
|
|
17
33
|
|
|
18
34
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
pip install "modmex-lambda[injector]"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
With Poetry:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
poetry add "modmex-lambda[injector]"
|
|
21
42
|
```
|
|
22
43
|
|
|
23
44
|
## API Gateway Resolvers
|
|
@@ -223,6 +244,69 @@ def get_user(
|
|
|
223
244
|
return repository.get_user(user_id)
|
|
224
245
|
```
|
|
225
246
|
|
|
247
|
+
For constructor-heavy services, install the optional `injector` extra and pass
|
|
248
|
+
an `InjectorDependencyResolver` to the app. `Depends()` without a callable uses
|
|
249
|
+
the parameter annotation as the dependency token.
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from typing import Annotated
|
|
253
|
+
|
|
254
|
+
from injector import Injector, Module, inject, provider, singleton
|
|
255
|
+
from modmex_lambda import ApiGatewayHttpResolver, Depends, InjectorDependencyResolver
|
|
256
|
+
from modmex_lambda.event_handler.params import Path
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Settings:
|
|
260
|
+
def __init__(self, tenant_id: str):
|
|
261
|
+
self.tenant_id = tenant_id
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class UserRepository:
|
|
265
|
+
def __init__(self, settings: Settings):
|
|
266
|
+
self.settings = settings
|
|
267
|
+
|
|
268
|
+
def get_user(self, user_id: int) -> dict:
|
|
269
|
+
return {"id": user_id, "tenant_id": self.settings.tenant_id}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class UserService:
|
|
273
|
+
def __init__(self, repository: UserRepository):
|
|
274
|
+
self.repository = repository
|
|
275
|
+
|
|
276
|
+
def get_user(self, user_id: int) -> dict:
|
|
277
|
+
return self.repository.get_user(user_id)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class AppModule(Module):
|
|
281
|
+
@singleton
|
|
282
|
+
@provider
|
|
283
|
+
def provide_settings(self) -> Settings:
|
|
284
|
+
return Settings(tenant_id="mx")
|
|
285
|
+
|
|
286
|
+
@singleton
|
|
287
|
+
@provider
|
|
288
|
+
@inject
|
|
289
|
+
def provide_repository(self, settings: Settings) -> UserRepository:
|
|
290
|
+
return UserRepository(settings)
|
|
291
|
+
|
|
292
|
+
@singleton
|
|
293
|
+
@provider
|
|
294
|
+
@inject
|
|
295
|
+
def provide_service(self, repository: UserRepository) -> UserService:
|
|
296
|
+
return UserService(repository)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
container = Injector([AppModule()])
|
|
300
|
+
app = ApiGatewayHttpResolver(dependency_resolver=InjectorDependencyResolver(container))
|
|
301
|
+
|
|
302
|
+
@app.get("/users/<user_id>")
|
|
303
|
+
def get_user(
|
|
304
|
+
user_id: Annotated[int, Path()],
|
|
305
|
+
service: Annotated[UserService, Depends()],
|
|
306
|
+
):
|
|
307
|
+
return service.get_user(user_id)
|
|
308
|
+
```
|
|
309
|
+
|
|
226
310
|
Disable dependency caching when a dependency must run every time:
|
|
227
311
|
|
|
228
312
|
```python
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: modmex-lambda
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling.
|
|
5
|
-
Author: Modmex
|
|
6
|
-
License: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Requires-Dist: modmex<2.0.0,>=1.1.10
|
|
10
|
-
Provides-Extra: dev
|
|
11
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
|
|
14
1
|
# modmex-lambda
|
|
15
2
|
|
|
16
3
|
Ultra-lightweight AWS Lambda utilities for API Gateway-first workloads.
|
|
@@ -26,11 +13,17 @@ logging with a small dependency footprint.
|
|
|
26
13
|
pip install modmex-lambda
|
|
27
14
|
```
|
|
28
15
|
|
|
29
|
-
|
|
16
|
+
|
|
17
|
+
To use the optional `injector` integration:
|
|
30
18
|
|
|
31
19
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
pip install "modmex-lambda[injector]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With Poetry:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
poetry add "modmex-lambda[injector]"
|
|
34
27
|
```
|
|
35
28
|
|
|
36
29
|
## API Gateway Resolvers
|
|
@@ -236,6 +229,69 @@ def get_user(
|
|
|
236
229
|
return repository.get_user(user_id)
|
|
237
230
|
```
|
|
238
231
|
|
|
232
|
+
For constructor-heavy services, install the optional `injector` extra and pass
|
|
233
|
+
an `InjectorDependencyResolver` to the app. `Depends()` without a callable uses
|
|
234
|
+
the parameter annotation as the dependency token.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from typing import Annotated
|
|
238
|
+
|
|
239
|
+
from injector import Injector, Module, inject, provider, singleton
|
|
240
|
+
from modmex_lambda import ApiGatewayHttpResolver, Depends, InjectorDependencyResolver
|
|
241
|
+
from modmex_lambda.event_handler.params import Path
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class Settings:
|
|
245
|
+
def __init__(self, tenant_id: str):
|
|
246
|
+
self.tenant_id = tenant_id
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class UserRepository:
|
|
250
|
+
def __init__(self, settings: Settings):
|
|
251
|
+
self.settings = settings
|
|
252
|
+
|
|
253
|
+
def get_user(self, user_id: int) -> dict:
|
|
254
|
+
return {"id": user_id, "tenant_id": self.settings.tenant_id}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class UserService:
|
|
258
|
+
def __init__(self, repository: UserRepository):
|
|
259
|
+
self.repository = repository
|
|
260
|
+
|
|
261
|
+
def get_user(self, user_id: int) -> dict:
|
|
262
|
+
return self.repository.get_user(user_id)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class AppModule(Module):
|
|
266
|
+
@singleton
|
|
267
|
+
@provider
|
|
268
|
+
def provide_settings(self) -> Settings:
|
|
269
|
+
return Settings(tenant_id="mx")
|
|
270
|
+
|
|
271
|
+
@singleton
|
|
272
|
+
@provider
|
|
273
|
+
@inject
|
|
274
|
+
def provide_repository(self, settings: Settings) -> UserRepository:
|
|
275
|
+
return UserRepository(settings)
|
|
276
|
+
|
|
277
|
+
@singleton
|
|
278
|
+
@provider
|
|
279
|
+
@inject
|
|
280
|
+
def provide_service(self, repository: UserRepository) -> UserService:
|
|
281
|
+
return UserService(repository)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
container = Injector([AppModule()])
|
|
285
|
+
app = ApiGatewayHttpResolver(dependency_resolver=InjectorDependencyResolver(container))
|
|
286
|
+
|
|
287
|
+
@app.get("/users/<user_id>")
|
|
288
|
+
def get_user(
|
|
289
|
+
user_id: Annotated[int, Path()],
|
|
290
|
+
service: Annotated[UserService, Depends()],
|
|
291
|
+
):
|
|
292
|
+
return service.get_user(user_id)
|
|
293
|
+
```
|
|
294
|
+
|
|
239
295
|
Disable dependency caching when a dependency must run every time:
|
|
240
296
|
|
|
241
297
|
```python
|
|
@@ -13,7 +13,12 @@ if TYPE_CHECKING:
|
|
|
13
13
|
)
|
|
14
14
|
from .event_handler.request import Request
|
|
15
15
|
from .event_sources import event_source
|
|
16
|
-
from .event_handler.dependencies.depends import
|
|
16
|
+
from .event_handler.dependencies.depends import (
|
|
17
|
+
DefaultDependencyResolver,
|
|
18
|
+
DependencyResolver,
|
|
19
|
+
Depends,
|
|
20
|
+
InjectorDependencyResolver,
|
|
21
|
+
)
|
|
17
22
|
from .logging import Logger
|
|
18
23
|
from .parser import event_parser, parse
|
|
19
24
|
from .validation import ModmexValidator, ValidationError
|
|
@@ -26,7 +31,10 @@ _EXPORTS = {
|
|
|
26
31
|
"parse": ("modmex_lambda.parser", "parse"),
|
|
27
32
|
"event_parser": ("modmex_lambda.parser", "event_parser"),
|
|
28
33
|
"event_source": ("modmex_lambda.event_sources", "event_source"),
|
|
34
|
+
"DefaultDependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "DefaultDependencyResolver"),
|
|
35
|
+
"DependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "DependencyResolver"),
|
|
29
36
|
"Depends": ("modmex_lambda.event_handler.dependencies.depends", "Depends"),
|
|
37
|
+
"InjectorDependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "InjectorDependencyResolver"),
|
|
30
38
|
"Logger": ("modmex_lambda.logging", "Logger"),
|
|
31
39
|
"ModmexValidator": ("modmex_lambda.validation", "ModmexValidator"),
|
|
32
40
|
"ValidationError": ("modmex_lambda.validation", "ValidationError"),
|
|
@@ -40,7 +48,10 @@ __all__ = [
|
|
|
40
48
|
"parse",
|
|
41
49
|
"event_parser",
|
|
42
50
|
"event_source",
|
|
51
|
+
"DefaultDependencyResolver",
|
|
52
|
+
"DependencyResolver",
|
|
43
53
|
"Depends",
|
|
54
|
+
"InjectorDependencyResolver",
|
|
44
55
|
"Logger",
|
|
45
56
|
"ModmexValidator",
|
|
46
57
|
"ValidationError",
|
|
@@ -10,13 +10,16 @@ if TYPE_CHECKING:
|
|
|
10
10
|
ApiGatewayRestResolver,
|
|
11
11
|
Response,
|
|
12
12
|
)
|
|
13
|
-
from .dependencies.depends import Depends
|
|
13
|
+
from .dependencies.depends import DefaultDependencyResolver, DependencyResolver, Depends, InjectorDependencyResolver
|
|
14
14
|
|
|
15
15
|
_EXPORTS = {
|
|
16
16
|
"ApiGatewayHttpResolver": ("modmex_lambda.event_handler.api_gateway", "ApiGatewayHttpResolver"),
|
|
17
17
|
"ApiGatewayRestResolver": ("modmex_lambda.event_handler.api_gateway", "ApiGatewayRestResolver"),
|
|
18
18
|
"Response": ("modmex_lambda.event_handler.api_gateway", "Response"),
|
|
19
|
+
"DefaultDependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "DefaultDependencyResolver"),
|
|
20
|
+
"DependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "DependencyResolver"),
|
|
19
21
|
"Depends": ("modmex_lambda.event_handler.dependencies.depends", "Depends"),
|
|
22
|
+
"InjectorDependencyResolver": ("modmex_lambda.event_handler.dependencies.depends", "InjectorDependencyResolver"),
|
|
20
23
|
"content_types": ("modmex_lambda.event_handler.content_types", None),
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -24,7 +27,10 @@ __all__ = [
|
|
|
24
27
|
"ApiGatewayHttpResolver",
|
|
25
28
|
"ApiGatewayRestResolver",
|
|
26
29
|
"Response",
|
|
30
|
+
"DefaultDependencyResolver",
|
|
31
|
+
"DependencyResolver",
|
|
27
32
|
"Depends",
|
|
33
|
+
"InjectorDependencyResolver",
|
|
28
34
|
"content_types",
|
|
29
35
|
]
|
|
30
36
|
|
|
@@ -27,6 +27,7 @@ from modmex_lambda.data_classes.api_gateway_proxy_event import APIGatewayProxyEv
|
|
|
27
27
|
from modmex_lambda.shared.types import AnyCallableT
|
|
28
28
|
from modmex_lambda.event_handler.types import IApiGatewayResolver
|
|
29
29
|
from modmex_lambda.event_handler.dependencies.dependency_middleware import DependencyMiddleware
|
|
30
|
+
from modmex_lambda.event_handler.dependencies.depends import DefaultDependencyResolver, DependencyResolver
|
|
30
31
|
from modmex_lambda.event_handler.middlewares import NextMiddleware
|
|
31
32
|
from modmex_lambda.event_handler.cors import CORSConfig
|
|
32
33
|
from modmex_lambda.shared.json_encoder import JSONEncoder
|
|
@@ -104,6 +105,7 @@ class ApiGatewayResolver(BaseRouter, IApiGatewayResolver):
|
|
|
104
105
|
strip_prefixes: list[str | Pattern] | None = None,
|
|
105
106
|
json_body_deserializer: Callable[[str], dict] | None = None,
|
|
106
107
|
logger: Any | None = None,
|
|
108
|
+
dependency_resolver: DependencyResolver | None = None,
|
|
107
109
|
) -> None:
|
|
108
110
|
self._cors = cors
|
|
109
111
|
self._cors_enabled = cors is not None
|
|
@@ -118,6 +120,7 @@ class ApiGatewayResolver(BaseRouter, IApiGatewayResolver):
|
|
|
118
120
|
self._router_middlewares: list[Middleware] = []
|
|
119
121
|
|
|
120
122
|
self._logger = logger
|
|
123
|
+
self.dependency_resolver = dependency_resolver or DefaultDependencyResolver()
|
|
121
124
|
self.dependency_overrides: dict[Callable[..., Any], Callable[..., Any]] = {}
|
|
122
125
|
self.current_event = None
|
|
123
126
|
self.current_context = None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from modmex_lambda.event_handler.dependencies.depends import (
|
|
2
|
+
DefaultDependencyResolver,
|
|
3
|
+
DependencyResolver,
|
|
4
|
+
Depends,
|
|
5
|
+
InjectorDependencyResolver,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DefaultDependencyResolver",
|
|
10
|
+
"DependencyResolver",
|
|
11
|
+
"Depends",
|
|
12
|
+
"InjectorDependencyResolver",
|
|
13
|
+
]
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/dependant.py
RENAMED
|
@@ -131,12 +131,15 @@ def get_dependant(
|
|
|
131
131
|
|
|
132
132
|
depends = _get_depends_from_annotation(param.annotation)
|
|
133
133
|
if depends is not None:
|
|
134
|
+
depends = depends.resolve_for_annotation(param.annotation)
|
|
134
135
|
_inherit_local_namespace(parent=call, dependency=depends.dependency)
|
|
135
136
|
dependant.dependencies.append(
|
|
136
137
|
DependencyParam(
|
|
137
138
|
param_name=param_name,
|
|
138
139
|
depends=depends,
|
|
139
|
-
dependant=
|
|
140
|
+
dependant=Dependant(call=depends.dependency)
|
|
141
|
+
if inspect.isclass(depends.dependency)
|
|
142
|
+
else get_dependant(path=path, call=depends.dependency),
|
|
140
143
|
),
|
|
141
144
|
)
|
|
142
145
|
continue
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/depends.py
RENAMED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import Annotated, Any, Callable, Protocol, get_args, get_origin, get_type_hints
|
|
6
7
|
|
|
7
8
|
from modmex_lambda.event_handler.dependencies.compat import ModelField
|
|
8
9
|
from modmex_lambda.event_handler.dependencies.types import CacheKey
|
|
@@ -43,15 +44,83 @@ class DependencyResolutionError(Exception):
|
|
|
43
44
|
"""Raised when a dependency cannot be resolved."""
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
class DependencyResolver(Protocol):
|
|
48
|
+
"""Resolves a dependency token into the value passed to a route handler."""
|
|
49
|
+
|
|
50
|
+
def resolve(
|
|
51
|
+
self,
|
|
52
|
+
dependency: Callable[..., Any] | type[Any],
|
|
53
|
+
*,
|
|
54
|
+
values: dict[str, Any] | None = None,
|
|
55
|
+
request: Request | None = None,
|
|
56
|
+
) -> Any:
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DefaultDependencyResolver:
|
|
61
|
+
"""Default resolver that calls dependency functions with solved values."""
|
|
62
|
+
|
|
63
|
+
def resolve(
|
|
64
|
+
self,
|
|
65
|
+
dependency: Callable[..., Any] | type[Any],
|
|
66
|
+
*,
|
|
67
|
+
values: dict[str, Any] | None = None,
|
|
68
|
+
request: Request | None = None,
|
|
69
|
+
) -> Any:
|
|
70
|
+
return dependency(**(values or {}))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class InjectorDependencyResolver:
|
|
74
|
+
"""Optional adapter for the ``injector`` package."""
|
|
75
|
+
|
|
76
|
+
def __init__(self, injector: Any) -> None:
|
|
77
|
+
self.injector = injector
|
|
78
|
+
|
|
79
|
+
def resolve(
|
|
80
|
+
self,
|
|
81
|
+
dependency: Callable[..., Any] | type[Any],
|
|
82
|
+
*,
|
|
83
|
+
values: dict[str, Any] | None = None,
|
|
84
|
+
request: Request | None = None,
|
|
85
|
+
) -> Any:
|
|
86
|
+
kwargs = values or {}
|
|
87
|
+
|
|
88
|
+
if hasattr(self.injector, "call_with_injection") and not inspect.isclass(dependency):
|
|
89
|
+
return self.injector.call_with_injection(dependency, kwargs=kwargs)
|
|
90
|
+
|
|
91
|
+
if not kwargs and inspect.isclass(dependency) and hasattr(self.injector, "get"):
|
|
92
|
+
return self.injector.get(dependency)
|
|
93
|
+
|
|
94
|
+
return dependency(**kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
46
97
|
class Depends:
|
|
47
|
-
def __init__(
|
|
48
|
-
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
dependency: Callable[..., Any] | type[Any] | None = None,
|
|
101
|
+
*,
|
|
102
|
+
use_cache: bool = True,
|
|
103
|
+
) -> None:
|
|
104
|
+
if dependency is not None and not callable(dependency):
|
|
49
105
|
raise DependencyResolutionError(
|
|
50
106
|
f"Depends() requires a callable, got {type(dependency).__name__}: {dependency!r}",
|
|
51
107
|
)
|
|
52
108
|
self.dependency = dependency
|
|
53
109
|
self.use_cache = use_cache
|
|
54
110
|
|
|
111
|
+
def resolve_for_annotation(self, annotation: Any) -> Depends:
|
|
112
|
+
if self.dependency is not None:
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
if get_origin(annotation) is Annotated:
|
|
116
|
+
dependency = get_args(annotation)[0]
|
|
117
|
+
if callable(dependency):
|
|
118
|
+
return Depends(dependency, use_cache=self.use_cache)
|
|
119
|
+
|
|
120
|
+
raise DependencyResolutionError(
|
|
121
|
+
"Depends() without a callable requires a callable parameter annotation",
|
|
122
|
+
)
|
|
123
|
+
|
|
55
124
|
|
|
56
125
|
class _DependencyNode:
|
|
57
126
|
"""Lightweight node in a dependency tree."""
|
|
@@ -101,12 +170,17 @@ def build_dependency_tree(func: Callable[..., Any]) -> DependencyTree:
|
|
|
101
170
|
depends = _get_depends_from_annotation(annotation)
|
|
102
171
|
if depends is None:
|
|
103
172
|
continue
|
|
173
|
+
depends = depends.resolve_for_annotation(annotation)
|
|
104
174
|
|
|
105
175
|
dependencies.append(
|
|
106
176
|
_DependencyNode(
|
|
107
177
|
param_name=param_name,
|
|
108
178
|
depends=depends,
|
|
109
|
-
sub_tree=
|
|
179
|
+
sub_tree=(
|
|
180
|
+
DependencyTree()
|
|
181
|
+
if inspect.isclass(depends.dependency)
|
|
182
|
+
else build_dependency_tree(depends.dependency)
|
|
183
|
+
),
|
|
110
184
|
),
|
|
111
185
|
)
|
|
112
186
|
|
|
@@ -119,12 +193,14 @@ def solve_dependencies(
|
|
|
119
193
|
request: Request | None = None,
|
|
120
194
|
dependency_overrides: dict[Callable[..., Any], Callable[..., Any]] | None = None,
|
|
121
195
|
dependency_cache: dict[Callable[..., Any], Any] | None = None,
|
|
196
|
+
dependency_resolver: DependencyResolver | None = None,
|
|
122
197
|
) -> dict[str, Any]:
|
|
123
198
|
"""Resolve all Depends parameters for a route or lightweight dependency tree."""
|
|
124
199
|
from modmex_lambda.event_handler.request import Request as RequestClass
|
|
125
200
|
|
|
126
201
|
cache = dependency_cache if dependency_cache is not None else {}
|
|
127
202
|
overrides = dependency_overrides or {}
|
|
203
|
+
resolver = dependency_resolver or DefaultDependencyResolver()
|
|
128
204
|
values: dict[str, Any] = {}
|
|
129
205
|
|
|
130
206
|
for dep in dependant.dependencies:
|
|
@@ -139,11 +215,12 @@ def solve_dependencies(
|
|
|
139
215
|
request=request,
|
|
140
216
|
dependency_overrides=overrides,
|
|
141
217
|
dependency_cache=cache,
|
|
218
|
+
dependency_resolver=resolver,
|
|
142
219
|
)
|
|
143
220
|
sub_values.update(_request_injection_values(dependency, request, RequestClass))
|
|
144
221
|
|
|
145
222
|
try:
|
|
146
|
-
solved = dependency
|
|
223
|
+
solved = resolver.resolve(dependency, values=sub_values, request=request)
|
|
147
224
|
except Exception as exc:
|
|
148
225
|
dep_name = getattr(dependency, "__name__", repr(dependency))
|
|
149
226
|
raise DependencyResolutionError(
|
|
@@ -177,7 +254,10 @@ def _request_injection_values(
|
|
|
177
254
|
__all__ = [
|
|
178
255
|
"Dependant",
|
|
179
256
|
"DependencyParam",
|
|
257
|
+
"DependencyResolver",
|
|
258
|
+
"DefaultDependencyResolver",
|
|
180
259
|
"Depends",
|
|
260
|
+
"InjectorDependencyResolver",
|
|
181
261
|
"_get_depends_from_annotation",
|
|
182
262
|
"build_dependency_tree",
|
|
183
263
|
"solve_dependencies",
|
|
@@ -4,6 +4,7 @@ from typing import Any, Callable, TypeVar, Union
|
|
|
4
4
|
|
|
5
5
|
from abc import ABC
|
|
6
6
|
from modmex_lambda.data_classes.api_gateway_proxy_event import APIGatewayProxyEvent, APIGatewayProxyEventV2
|
|
7
|
+
from modmex_lambda.event_handler.dependencies.depends import DependencyResolver
|
|
7
8
|
from modmex_lambda.event_handler.request import Request
|
|
8
9
|
from modmex_lambda.event_handler.response import Response
|
|
9
10
|
|
|
@@ -11,6 +12,7 @@ from modmex_lambda.event_handler.response import Response
|
|
|
11
12
|
class IApiGatewayResolver(ABC):
|
|
12
13
|
context: dict[str, Any]
|
|
13
14
|
current_event: Union[APIGatewayProxyEvent, APIGatewayProxyEventV2]
|
|
15
|
+
dependency_resolver: DependencyResolver
|
|
14
16
|
dependency_overrides: dict[Callable[..., Any], Callable[..., Any]]
|
|
15
17
|
_dependency_middleware: Callable[..., Any]
|
|
16
18
|
|
|
@@ -167,6 +167,22 @@ files = [
|
|
|
167
167
|
]
|
|
168
168
|
markers = {main = "extra == \"dev\""}
|
|
169
169
|
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "injector"
|
|
172
|
+
version = "0.24.0"
|
|
173
|
+
description = "Injector - Python dependency injection framework, inspired by Guice"
|
|
174
|
+
optional = true
|
|
175
|
+
python-versions = "*"
|
|
176
|
+
groups = ["main"]
|
|
177
|
+
markers = "extra == \"injector\""
|
|
178
|
+
files = [
|
|
179
|
+
{file = "injector-0.24.0-py3-none-any.whl", hash = "sha256:47294c7a7fdb811f0d1b442a1e0152bb2fc28b2ccaaba4cba44e5e125e0da2d0"},
|
|
180
|
+
{file = "injector-0.24.0.tar.gz", hash = "sha256:e85a75d1516cff2f03170f3fd1219f56acb25c9a05e307819ae0dcde3dad3d3f"},
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
[package.extras]
|
|
184
|
+
dev = ["black (==24.3.0) ; implementation_name == \"cpython\"", "build (==1.0.3)", "check-manifest (==0.49)", "click (==8.1.7)", "coverage[toml] (==7.3.2)", "exceptiongroup (==1.2.0)", "importlib-metadata (==7.0.0)", "iniconfig (==2.0.0)", "mypy (==1.7.1) ; implementation_name == \"cpython\"", "mypy-extensions (==1.0.0)", "packaging (==25.0)", "pathspec (==0.12.1)", "platformdirs (==4.1.0)", "pluggy (==1.3.0)", "pyproject-hooks (==1.0.0)", "pytest (==7.4.3)", "pytest-cov (==4.1.0)", "tomli (==2.0.1)", "typing-extensions (==4.9.0) ; python_version < \"3.9\"", "zipp (==3.19.1)"]
|
|
185
|
+
|
|
170
186
|
[[package]]
|
|
171
187
|
name = "modmex"
|
|
172
188
|
version = "1.1.10"
|
|
@@ -432,8 +448,9 @@ markers = {main = "extra == \"dev\" and python_version == \"3.10\"", dev = "pyth
|
|
|
432
448
|
|
|
433
449
|
[extras]
|
|
434
450
|
dev = ["pytest"]
|
|
451
|
+
injector = ["injector"]
|
|
435
452
|
|
|
436
453
|
[metadata]
|
|
437
454
|
lock-version = "2.1"
|
|
438
455
|
python-versions = ">=3.10"
|
|
439
|
-
content-hash = "
|
|
456
|
+
content-hash = "76e16dbf080a877a8ede81c719d72cd7a6023c75506305088b55d814d3cae4a4"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modmex-lambda"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
18
18
|
dev = ["pytest>=8.0"]
|
|
19
|
+
injector = ["injector>=0.24.0,<1.0.0"]
|
|
19
20
|
|
|
20
21
|
[tool.pytest.ini_options]
|
|
21
22
|
pythonpath = ["."]
|
|
@@ -29,6 +29,35 @@ class CreateUserRequest:
|
|
|
29
29
|
self.age = age
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class Settings:
|
|
33
|
+
def __init__(self, tenant_id: str) -> None:
|
|
34
|
+
self.tenant_id = tenant_id
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UserRepository:
|
|
38
|
+
def __init__(self, settings: Settings) -> None:
|
|
39
|
+
self.settings = settings
|
|
40
|
+
|
|
41
|
+
def get_user(self, user_id: int) -> dict[str, str | int]:
|
|
42
|
+
return {"id": user_id, "tenant_id": self.settings.tenant_id}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UserService:
|
|
46
|
+
def __init__(self, repository: UserRepository) -> None:
|
|
47
|
+
self.repository = repository
|
|
48
|
+
|
|
49
|
+
def get_user(self, user_id: int) -> dict[str, str | int]:
|
|
50
|
+
return self.repository.get_user(user_id)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AuditService:
|
|
54
|
+
def __init__(self, tenant_id: str) -> None:
|
|
55
|
+
self.tenant_id = tenant_id
|
|
56
|
+
|
|
57
|
+
def record(self, user_id: int) -> dict[str, str | int]:
|
|
58
|
+
return {"user_id": user_id, "tenant_id": self.tenant_id}
|
|
59
|
+
|
|
60
|
+
|
|
32
61
|
def test_public_api_requires_explicit_api_gateway_resolver() -> None:
|
|
33
62
|
assert modmex_lambda.ApiGatewayHttpResolver is ApiGatewayHttpResolver
|
|
34
63
|
assert modmex_lambda.ApiGatewayRestResolver is ApiGatewayRestResolver
|
|
@@ -437,6 +466,97 @@ def test_dependency_injection_without_cache() -> None:
|
|
|
437
466
|
assert response_body(response) == {"a": 1, "b": 2}
|
|
438
467
|
|
|
439
468
|
|
|
469
|
+
def test_dependency_injection_uses_custom_dependency_resolver_for_annotation_token() -> None:
|
|
470
|
+
class UserService:
|
|
471
|
+
def __init__(self, tenant_id: str) -> None:
|
|
472
|
+
self.tenant_id = tenant_id
|
|
473
|
+
|
|
474
|
+
class Resolver:
|
|
475
|
+
def resolve(self, dependency, *, values=None, request=None):
|
|
476
|
+
assert dependency is UserService
|
|
477
|
+
assert values == {}
|
|
478
|
+
assert request.headers["x-tenant-id"] == "mx"
|
|
479
|
+
return UserService(tenant_id="mx")
|
|
480
|
+
|
|
481
|
+
app = ApiGatewayHttpResolver(dependency_resolver=Resolver())
|
|
482
|
+
|
|
483
|
+
@app.get("/di-resolver")
|
|
484
|
+
def handler(service: Annotated[UserService, Depends()]):
|
|
485
|
+
return {"tenant_id": service.tenant_id}
|
|
486
|
+
|
|
487
|
+
response = app.resolve(http_v2_event("GET", "/di-resolver", headers={"x-tenant-id": "mx"}), object())
|
|
488
|
+
|
|
489
|
+
assert response_body(response) == {"tenant_id": "mx"}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_http_resolver_supports_real_injector_class_token_dependency() -> None:
|
|
493
|
+
pytest.importorskip("injector")
|
|
494
|
+
from injector import Injector, Module, inject, provider, singleton
|
|
495
|
+
from modmex_lambda import InjectorDependencyResolver
|
|
496
|
+
|
|
497
|
+
class MyModule(Module):
|
|
498
|
+
@singleton
|
|
499
|
+
@provider
|
|
500
|
+
def provide_settings(self) -> Settings:
|
|
501
|
+
return Settings(tenant_id="mx")
|
|
502
|
+
|
|
503
|
+
@singleton
|
|
504
|
+
@provider
|
|
505
|
+
@inject
|
|
506
|
+
def provide_repository(self, settings: Settings) -> UserRepository:
|
|
507
|
+
return UserRepository(settings)
|
|
508
|
+
|
|
509
|
+
@singleton
|
|
510
|
+
@provider
|
|
511
|
+
@inject
|
|
512
|
+
def provide_service(self, repository: UserRepository) -> UserService:
|
|
513
|
+
return UserService(repository)
|
|
514
|
+
|
|
515
|
+
container = Injector([MyModule()])
|
|
516
|
+
app = ApiGatewayHttpResolver(dependency_resolver=InjectorDependencyResolver(container))
|
|
517
|
+
|
|
518
|
+
@app.get("/injector/users/<user_id>")
|
|
519
|
+
def handler(
|
|
520
|
+
user_id: Annotated[int, Path()],
|
|
521
|
+
service: Annotated[UserService, Depends()],
|
|
522
|
+
):
|
|
523
|
+
return service.get_user(user_id)
|
|
524
|
+
|
|
525
|
+
response = app.resolve(http_v2_event("GET", "/injector/users/42"), object())
|
|
526
|
+
|
|
527
|
+
assert response_body(response) == {"id": 42, "tenant_id": "mx"}
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def test_rest_resolver_supports_real_injector_factory_dependency() -> None:
|
|
531
|
+
pytest.importorskip("injector")
|
|
532
|
+
from injector import Injector, Module, inject, provider, singleton
|
|
533
|
+
from modmex_lambda import InjectorDependencyResolver
|
|
534
|
+
|
|
535
|
+
class MyModule(Module):
|
|
536
|
+
@singleton
|
|
537
|
+
@provider
|
|
538
|
+
def provide_settings(self) -> Settings:
|
|
539
|
+
return Settings(tenant_id="mx")
|
|
540
|
+
|
|
541
|
+
@inject
|
|
542
|
+
def get_audit_service(settings: Settings) -> AuditService:
|
|
543
|
+
return AuditService(settings.tenant_id)
|
|
544
|
+
|
|
545
|
+
container = Injector([MyModule()])
|
|
546
|
+
app = ApiGatewayRestResolver(dependency_resolver=InjectorDependencyResolver(container))
|
|
547
|
+
|
|
548
|
+
@app.get("/injector/audit/<user_id>")
|
|
549
|
+
def handler(
|
|
550
|
+
user_id: Annotated[int, Path()],
|
|
551
|
+
audit: Annotated[AuditService, Depends(get_audit_service)],
|
|
552
|
+
):
|
|
553
|
+
return audit.record(user_id)
|
|
554
|
+
|
|
555
|
+
response = app.resolve(rest_event("GET", "/injector/audit/7"), object())
|
|
556
|
+
|
|
557
|
+
assert response_body(response) == {"user_id": 7, "tenant_id": "mx"}
|
|
558
|
+
|
|
559
|
+
|
|
440
560
|
def test_validation_error_maps_to_400() -> None:
|
|
441
561
|
app = ApiGatewayHttpResolver()
|
|
442
562
|
|
|
@@ -39,6 +39,7 @@ from modmex_lambda.event_handler.dependencies.depends import (
|
|
|
39
39
|
DependencyParam,
|
|
40
40
|
DependencyResolutionError,
|
|
41
41
|
Depends,
|
|
42
|
+
InjectorDependencyResolver,
|
|
42
43
|
build_dependency_tree,
|
|
43
44
|
solve_dependencies,
|
|
44
45
|
)
|
|
@@ -120,6 +121,15 @@ def endpoint_with_broken_dependency(value: Annotated[str, Depends(broken_depende
|
|
|
120
121
|
return None
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
class ComplexService:
|
|
125
|
+
def __init__(self, dependency: object) -> None:
|
|
126
|
+
self.dependency = dependency
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def endpoint_with_annotation_dependency(service: Annotated[ComplexService, Depends()]) -> None:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
123
133
|
def _field(
|
|
124
134
|
name: str,
|
|
125
135
|
annotation: Any,
|
|
@@ -149,6 +159,15 @@ def test_depends_rejects_non_callable_and_extracts_annotated_dependency() -> Non
|
|
|
149
159
|
assert compat._normalize_errors([{"loc": ("x",), "type": "missing"}]) == [{"loc": ("x",), "type": "missing"}]
|
|
150
160
|
|
|
151
161
|
|
|
162
|
+
def test_depends_without_callable_uses_parameter_annotation_as_dependency_token() -> None:
|
|
163
|
+
tree = build_dependency_tree(endpoint_with_annotation_dependency)
|
|
164
|
+
|
|
165
|
+
assert len(tree.dependencies) == 1
|
|
166
|
+
assert tree.dependencies[0].param_name == "service"
|
|
167
|
+
assert tree.dependencies[0].depends.dependency is ComplexService
|
|
168
|
+
assert tree.dependencies[0].dependant.dependencies == []
|
|
169
|
+
|
|
170
|
+
|
|
152
171
|
def test_solve_dependencies_supports_cache_overrides_request_and_errors() -> None:
|
|
153
172
|
CALLS["token"] = 0
|
|
154
173
|
event = APIGatewayProxyEventV2(http_v2_event("GET", "/items", headers={"x-tenant-id": "mx"}))
|
|
@@ -170,6 +189,34 @@ def test_solve_dependencies_supports_cache_overrides_request_and_errors() -> Non
|
|
|
170
189
|
solve_dependencies(dependant=build_dependency_tree(endpoint_with_broken_dependency))
|
|
171
190
|
|
|
172
191
|
|
|
192
|
+
def test_solve_dependencies_accepts_custom_dependency_resolver() -> None:
|
|
193
|
+
class Resolver:
|
|
194
|
+
def resolve(self, dependency, *, values=None, request=None):
|
|
195
|
+
assert dependency is ComplexService
|
|
196
|
+
assert values == {}
|
|
197
|
+
return ComplexService(dependency="container")
|
|
198
|
+
|
|
199
|
+
values = solve_dependencies(
|
|
200
|
+
dependant=build_dependency_tree(endpoint_with_annotation_dependency),
|
|
201
|
+
dependency_resolver=Resolver(),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
assert values["service"].dependency == "container"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_injector_dependency_resolver_uses_injector_get_for_class_tokens() -> None:
|
|
208
|
+
service = ComplexService(dependency="injector")
|
|
209
|
+
|
|
210
|
+
class FakeInjector:
|
|
211
|
+
def get(self, dependency):
|
|
212
|
+
assert dependency is ComplexService
|
|
213
|
+
return service
|
|
214
|
+
|
|
215
|
+
resolver = InjectorDependencyResolver(FakeInjector())
|
|
216
|
+
|
|
217
|
+
assert resolver.resolve(ComplexService) is service
|
|
218
|
+
|
|
219
|
+
|
|
173
220
|
def test_solve_dependencies_honors_use_cache_false() -> None:
|
|
174
221
|
CALLS["counter"] = 0
|
|
175
222
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/api_gateway_proxy_event.py
RENAMED
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/data_classes/cognito_user_pool_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/compat.py
RENAMED
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/params.py
RENAMED
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/dependencies/types.py
RENAMED
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/exception_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/modmex_lambda/event_handler/routing_fallbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/data_classes/test_api_gateway_proxy_event.py
RENAMED
|
File without changes
|
{modmex_lambda-0.1.0 → modmex_lambda-0.2.0}/tests/data_classes/test_cognito_user_pool_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|