wellapi 0.10.2__tar.gz → 0.10.4__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 (60) hide show
  1. {wellapi-0.10.2 → wellapi-0.10.4}/PKG-INFO +1 -1
  2. {wellapi-0.10.2 → wellapi-0.10.4}/pyproject.toml +1 -1
  3. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/dependencies/utils.py +7 -5
  4. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/routing.py +29 -25
  5. wellapi-0.10.4/tests/test_request_handler_body.py +39 -0
  6. {wellapi-0.10.2 → wellapi-0.10.4}/uv.lock +225 -225
  7. {wellapi-0.10.2 → wellapi-0.10.4}/.github/workflows/build.pipeline.yml +0 -0
  8. {wellapi-0.10.2 → wellapi-0.10.4}/.gitignore +0 -0
  9. {wellapi-0.10.2 → wellapi-0.10.4}/.python-version +0 -0
  10. {wellapi-0.10.2 → wellapi-0.10.4}/README.md +0 -0
  11. {wellapi-0.10.2 → wellapi-0.10.4}/docs/framework-usage.md +0 -0
  12. {wellapi-0.10.2 → wellapi-0.10.4}/docs/superpowers/plans/2026-05-29-otel-native-telemetry.md +0 -0
  13. {wellapi-0.10.2 → wellapi-0.10.4}/docs/superpowers/specs/2026-05-29-otel-native-telemetry-design.md +0 -0
  14. {wellapi-0.10.2 → wellapi-0.10.4}/docs/telemetry.md +0 -0
  15. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/__init__.py +0 -0
  16. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/__main__.py +0 -0
  17. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/applications.py +0 -0
  18. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/awsmodel.py +0 -0
  19. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/build/__init__.py +0 -0
  20. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/build/cdk.py +0 -0
  21. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/build/packager.py +0 -0
  22. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/cli/__init__.py +0 -0
  23. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/cli/main.py +0 -0
  24. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/convertors.py +0 -0
  25. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/datastructures.py +0 -0
  26. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/dependencies/__init__.py +0 -0
  27. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/dependencies/models.py +0 -0
  28. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/exception_handlers.py +0 -0
  29. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/exceptions.py +0 -0
  30. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/local/__init__.py +0 -0
  31. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/local/router.py +0 -0
  32. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/local/server.py +0 -0
  33. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/__init__.py +0 -0
  34. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/base.py +0 -0
  35. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/cors.py +0 -0
  36. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/error.py +0 -0
  37. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/exceptions.py +0 -0
  38. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/middleware/main.py +0 -0
  39. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/models.py +0 -0
  40. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/openapi/__init__.py +0 -0
  41. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/openapi/docs.py +0 -0
  42. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/openapi/models.py +0 -0
  43. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/openapi/security_model.py +0 -0
  44. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/openapi/utils.py +0 -0
  45. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/params.py +0 -0
  46. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/security.py +0 -0
  47. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/telemetry/__init__.py +0 -0
  48. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/telemetry/attributes.py +0 -0
  49. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/telemetry/config.py +0 -0
  50. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/telemetry/flush.py +0 -0
  51. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/telemetry/middleware.py +0 -0
  52. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/testclient.py +0 -0
  53. {wellapi-0.10.2 → wellapi-0.10.4}/src/wellapi/utils.py +0 -0
  54. {wellapi-0.10.2 → wellapi-0.10.4}/tests/conftest.py +0 -0
  55. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/__init__.py +0 -0
  56. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/test_applications.py +0 -0
  57. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/test_attributes.py +0 -0
  58. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/test_config.py +0 -0
  59. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/test_flush.py +0 -0
  60. {wellapi-0.10.2 → wellapi-0.10.4}/tests/telemetry/test_middleware.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wellapi
3
- Version: 0.10.2
3
+ Version: 0.10.4
4
4
  Summary: A simple web framework for aws lambda
5
5
  Author-email: romayuhym <romayuhym@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wellapi"
3
- version = "0.10.2"
3
+ version = "0.10.4"
4
4
  description = "A simple web framework for aws lambda"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -907,11 +907,13 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
907
907
  def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]:
908
908
  if not fields:
909
909
  return fields
910
- first_field = fields[0]
911
- if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
912
- fields_to_extract = get_cached_model_fields(first_field.type_)
913
- return fields_to_extract
914
- return fields
910
+ flat_fields: List[ModelField] = []
911
+ for field in fields:
912
+ if lenient_issubclass(field.type_, BaseModel):
913
+ flat_fields.extend(get_cached_model_fields(field.type_))
914
+ else:
915
+ flat_fields.append(field)
916
+ return flat_fields
915
917
 
916
918
 
917
919
  def get_flat_params(dependant: Dependant) -> List[ModelField]:
@@ -9,7 +9,7 @@ from pydantic.main import IncEx
9
9
  from wellapi.convertors import CONVERTOR_TYPES
10
10
  from wellapi.datastructures import Default, DefaultPlaceholder
11
11
  from wellapi.dependencies.models import Dependant, ModelField
12
- from wellapi.dependencies.utils import solve_dependencies
12
+ from wellapi.dependencies.utils import get_flat_dependant, solve_dependencies
13
13
  from wellapi.exceptions import (
14
14
  HTTPException,
15
15
  RequestValidationError,
@@ -100,33 +100,37 @@ def get_request_handler(
100
100
  else:
101
101
  actual_response_class = response_class
102
102
 
103
+ has_body_params = bool(get_flat_dependant(dependant).body_params)
104
+
103
105
  @functools.wraps(dependant.call)
104
106
  def app(request: RequestAPIGateway) -> ResponseAPIGateway:
105
107
  response: ResponseAPIGateway | None = None
106
- try:
107
- body: Any = request.json()
108
- except json.JSONDecodeError as e:
109
- validation_error = RequestValidationError(
110
- [
111
- {
112
- "type": "json_invalid",
113
- "loc": ("body", e.pos),
114
- "msg": "JSON decode error",
115
- "input": {},
116
- "ctx": {"error": e.msg},
117
- }
118
- ],
119
- body=e.doc,
120
- )
121
- raise validation_error from e
122
- except HTTPException:
123
- # If a middleware raises an HTTPException, it should be raised again
124
- raise
125
- except Exception as e:
126
- http_error = HTTPException(
127
- status_code=400, detail="There was an error parsing the body"
128
- )
129
- raise http_error from e
108
+ body: Any = None
109
+ if has_body_params:
110
+ try:
111
+ body = request.json()
112
+ except json.JSONDecodeError as e:
113
+ validation_error = RequestValidationError(
114
+ [
115
+ {
116
+ "type": "json_invalid",
117
+ "loc": ("body", e.pos),
118
+ "msg": "JSON decode error",
119
+ "input": {},
120
+ "ctx": {"error": e.msg},
121
+ }
122
+ ],
123
+ body=e.doc,
124
+ )
125
+ raise validation_error from e
126
+ except HTTPException:
127
+ # If a middleware raises an HTTPException, it should be raised again
128
+ raise
129
+ except Exception as e:
130
+ http_error = HTTPException(
131
+ status_code=400, detail="There was an error parsing the body"
132
+ )
133
+ raise http_error from e
130
134
  errors: list[Any] = []
131
135
  solved_result = solve_dependencies(
132
136
  request=request,
@@ -0,0 +1,39 @@
1
+ """Non-JSON request bodies must reach handlers that declare no body params."""
2
+
3
+ from typing import Annotated
4
+
5
+ import pytest
6
+
7
+ from wellapi import Body, WellApi
8
+ from wellapi.exceptions import RequestValidationError
9
+ from wellapi.models import RequestAPIGateway
10
+
11
+
12
+ def _make_request(api_gateway_event, body: str) -> RequestAPIGateway:
13
+ event = api_gateway_event | {"httpMethod": "POST", "body": body}
14
+ return RequestAPIGateway.create_request_from_event(event)
15
+
16
+
17
+ def test_non_json_body_reaches_handler_without_body_params(api_gateway_event):
18
+ app = WellApi()
19
+
20
+ @app.post("/relay")
21
+ def relay(request: RequestAPIGateway) -> dict:
22
+ return {"raw": request.raw_event.body}
23
+
24
+ lam = next(lambda_ for lambda_ in app.lambdas if lambda_.name == "relay")
25
+ response = lam.app(_make_request(api_gateway_event, "code=abc&state=x%2Fy"))
26
+
27
+ assert response.statusCode == 200
28
+
29
+
30
+ def test_invalid_json_with_body_params_still_rejected(api_gateway_event):
31
+ app = WellApi()
32
+
33
+ @app.post("/strict")
34
+ def strict(name: Annotated[str, Body(embed=True)]) -> dict:
35
+ return {"name": name}
36
+
37
+ lam = next(lambda_ for lambda_ in app.lambdas if lambda_.name == "strict")
38
+ with pytest.raises(RequestValidationError):
39
+ lam.app(_make_request(api_gateway_event, "not json"))