cadwyn 4.2.1__tar.gz → 4.2.3__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.

Potentially problematic release.


This version of cadwyn might be problematic. Click here for more details.

Files changed (27) hide show
  1. {cadwyn-4.2.1 → cadwyn-4.2.3}/PKG-INFO +3 -4
  2. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_asts.py +2 -2
  3. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/applications.py +9 -1
  4. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/middleware.py +4 -4
  5. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/schema_generation.py +3 -3
  6. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/versions.py +19 -8
  7. {cadwyn-4.2.1 → cadwyn-4.2.3}/pyproject.toml +4 -7
  8. {cadwyn-4.2.1 → cadwyn-4.2.3}/LICENSE +0 -0
  9. {cadwyn-4.2.1 → cadwyn-4.2.3}/README.md +0 -0
  10. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/__init__.py +0 -0
  11. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/__main__.py +0 -0
  12. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_importer.py +0 -0
  13. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_render.py +0 -0
  14. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_utils.py +0 -0
  15. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/changelogs.py +0 -0
  16. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/exceptions.py +0 -0
  17. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/py.typed +0 -0
  18. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/route_generation.py +0 -0
  19. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/routing.py +0 -0
  20. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/static/__init__.py +0 -0
  21. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/static/docs.html +0 -0
  22. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/__init__.py +0 -0
  23. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/common.py +0 -0
  24. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/data.py +0 -0
  25. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/endpoints.py +0 -0
  26. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/enums.py +0 -0
  27. {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/schemas.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 4.2.1
3
+ Version: 4.2.3
4
4
  Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
5
5
  Home-page: https://github.com/zmievsa/cadwyn
6
6
  License: MIT
@@ -31,14 +31,13 @@ Classifier: Topic :: Software Development :: Libraries
31
31
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
32
32
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
33
  Classifier: Typing :: Typed
34
- Provides-Extra: cli
35
34
  Requires-Dist: backports-strenum (>=1.3.1,<2.0.0) ; python_version < "3.11"
36
- Requires-Dist: fastapi (>=0.110.0)
35
+ Requires-Dist: fastapi[standard] (>=0.112.3)
37
36
  Requires-Dist: issubclass (>=0.1.2,<0.2.0)
38
37
  Requires-Dist: jinja2 (>=3.1.2)
39
38
  Requires-Dist: pydantic (>=2.0.0)
40
39
  Requires-Dist: starlette (>=0.30.0)
41
- Requires-Dist: typer (>=0.7.0) ; extra == "cli"
40
+ Requires-Dist: typer (>=0.7.0)
42
41
  Requires-Dist: typing-extensions
43
42
  Project-URL: Documentation, https://docs.cadwyn.dev
44
43
  Project-URL: Repository, https://github.com/zmievsa/cadwyn
@@ -28,7 +28,7 @@ _BaseGenericAlias = cast(type, type(List[int])).mro()[1] # noqa: UP006
28
28
  GenericAliasUnion = GenericAlias | _BaseGenericAlias
29
29
 
30
30
 
31
- def get_fancy_repr(value: Any):
31
+ def get_fancy_repr(value: Any) -> Any:
32
32
  if isinstance(value, annotated_types.GroupedMetadata) and hasattr(type(value), "__dataclass_fields__"):
33
33
  return transform_grouped_metadata(value)
34
34
  if isinstance(value, list | tuple | set | frozenset):
@@ -85,7 +85,7 @@ def transform_generic_alias(value: GenericAliasUnion) -> Any:
85
85
  return f"{get_fancy_repr(get_origin(value))}[{', '.join(get_fancy_repr(a) for a in get_args(value))}]"
86
86
 
87
87
 
88
- def transform_none(_: NoneType) -> Any:
88
+ def transform_none(_: type[None]) -> Any:
89
89
  return "None"
90
90
 
91
91
 
@@ -269,12 +269,20 @@ class Cadwyn(FastAPI):
269
269
  else:
270
270
  raise not_found_error
271
271
 
272
+ # Add root path to servers when mounted as sub-app or proxy is used
273
+ urls = (server_data.get("url") for server_data in self.servers)
274
+ server_urls = {url for url in urls if url}
275
+ root_path = self._extract_root_path(req)
276
+ if root_path and root_path not in server_urls and self.root_path_in_servers:
277
+ self.servers.insert(0, {"url": root_path})
278
+
272
279
  return JSONResponse(
273
280
  get_openapi(
274
281
  title=self.title,
275
282
  version=formatted_version,
276
283
  openapi_version=self.openapi_version,
277
284
  description=self.description,
285
+ summary=self.summary,
278
286
  terms_of_service=self.terms_of_service,
279
287
  contact=self.contact,
280
288
  license_info=self.license_info,
@@ -320,7 +328,7 @@ class Cadwyn(FastAPI):
320
328
 
321
329
  def _render_docs_dashboard(self, req: Request, docs_url: str):
322
330
  base_host = str(req.base_url).rstrip("/")
323
- root_path = req.scope.get("root_path", "")
331
+ root_path = self._extract_root_path(req)
324
332
  base_url = base_host + root_path
325
333
  table = {version: f"{base_url}{docs_url}?version={version}" for version in self.router.sorted_versions}
326
334
  if self._there_are_public_unversioned_routes():
@@ -64,11 +64,11 @@ class HeaderVersioningMiddleware(BaseHTTPMiddleware):
64
64
  request=request,
65
65
  dependant=self.version_header_validation_dependant,
66
66
  async_exit_stack=async_exit_stack,
67
+ embed_body_fields=False,
67
68
  )
68
- values, errors, *_ = solved_result
69
- if errors:
70
- return self.default_response_class(status_code=422, content=_normalize_errors(errors))
71
- api_version = cast(date, values[self.api_version_header_name.replace("-", "_")])
69
+ if solved_result.errors:
70
+ return self.default_response_class(status_code=422, content=_normalize_errors(solved_result.errors))
71
+ api_version = cast(date, solved_result.values[self.api_version_header_name.replace("-", "_")])
72
72
  self.api_version_var.set(api_version)
73
73
 
74
74
  response = await call_next(request)
@@ -341,12 +341,12 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
341
341
  per_field_validators = {
342
342
  name: validator.decorator(*validator.fields, **validator.kwargs)(validator.func)
343
343
  for name, validator in self.validators.items()
344
- if not validator.is_deleted and type(validator) == _PerFieldValidatorWrapper
344
+ if not validator.is_deleted and type(validator) == _PerFieldValidatorWrapper # noqa: E721
345
345
  }
346
346
  root_validators = {
347
347
  name: validator.decorator(**validator.kwargs)(validator.func)
348
348
  for name, validator in self.validators.items()
349
- if not validator.is_deleted and type(validator) == _ValidatorWrapper
349
+ if not validator.is_deleted and type(validator) == _ValidatorWrapper # noqa: E721
350
350
  }
351
351
  fields = {name: field.generate_field_copy(generator) for name, field in self.fields.items()}
352
352
  model_copy = type(self.cls)(
@@ -441,7 +441,7 @@ class _AnnotationTransformer:
441
441
  def migrate_route_to_version(self, route: fastapi.routing.APIRoute, *, ignore_response_model: bool = False):
442
442
  if route.response_model is not None and not ignore_response_model:
443
443
  route.response_model = self.change_version_of_annotation(route.response_model)
444
- route.response_field = fastapi.utils.create_response_field(
444
+ route.response_field = fastapi.utils.create_model_field(
445
445
  name="Response_" + route.unique_id,
446
446
  type_=route.response_model,
447
447
  mode="serialization",
@@ -350,7 +350,9 @@ class VersionBundle:
350
350
  request_info: RequestInfo,
351
351
  current_version: VersionDate,
352
352
  head_route: APIRoute,
353
+ *,
353
354
  exit_stack: AsyncExitStack,
355
+ embed_body_fields: bool = False,
354
356
  ) -> dict[str, Any]:
355
357
  method = request.method
356
358
  for v in reversed(self.versions):
@@ -367,19 +369,20 @@ class VersionBundle:
367
369
  request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
368
370
  del request._headers
369
371
  # Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
370
- dependencies, errors, _, _, _ = await solve_dependencies(
372
+ result = await solve_dependencies(
371
373
  request=request,
372
374
  response=response,
373
375
  dependant=head_dependant,
374
376
  body=request_info.body,
375
377
  dependency_overrides_provider=head_route.dependency_overrides_provider,
376
378
  async_exit_stack=exit_stack,
379
+ embed_body_fields=embed_body_fields,
377
380
  )
378
- if errors:
381
+ if result.errors:
379
382
  raise CadwynHeadRequestValidationError(
380
- _normalize_errors(errors), body=request_info.body, version=current_version
383
+ _normalize_errors(result.errors), body=request_info.body, version=current_version
381
384
  )
382
- return dependencies
385
+ return result.values
383
386
 
384
387
  def _migrate_response(
385
388
  self,
@@ -452,7 +455,8 @@ class VersionBundle:
452
455
  response_param,
453
456
  route,
454
457
  head_route,
455
- exit_stack,
458
+ exit_stack=exit_stack,
459
+ embed_body_fields=route._embed_body_fields,
456
460
  )
457
461
 
458
462
  response = await self._convert_endpoint_response_to_version(
@@ -479,7 +483,7 @@ class VersionBundle:
479
483
  if response_param_name == _CADWYN_RESPONSE_PARAM_NAME:
480
484
  _add_keyword_only_parameter(decorator, _CADWYN_RESPONSE_PARAM_NAME, FastapiResponse)
481
485
 
482
- return decorator
486
+ return decorator # pyright: ignore[reportReturnType]
483
487
 
484
488
  return wrapper
485
489
 
@@ -524,10 +528,14 @@ class VersionBundle:
524
528
  if isinstance(response_or_response_body, StreamingResponse | FileResponse):
525
529
  body = None
526
530
  elif response_or_response_body.body:
527
- if isinstance(response_or_response_body, JSONResponse) or raised_exception is not None:
531
+ if (isinstance(response_or_response_body, JSONResponse) or raised_exception is not None) and isinstance(
532
+ response_or_response_body.body, str | bytes
533
+ ):
528
534
  body = json.loads(response_or_response_body.body)
529
- else:
535
+ elif isinstance(response_or_response_body.body, bytes):
530
536
  body = response_or_response_body.body.decode(response_or_response_body.charset)
537
+ else: # pragma: no cover # I don't see a good use case here yet
538
+ body = response_or_response_body.body
531
539
  else:
532
540
  body = None
533
541
  # TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
@@ -614,7 +622,9 @@ class VersionBundle:
614
622
  response: FastapiResponse,
615
623
  route: APIRoute,
616
624
  head_route: APIRoute,
625
+ *,
617
626
  exit_stack: AsyncExitStack,
627
+ embed_body_fields: bool,
618
628
  ) -> dict[str, Any]:
619
629
  request: FastapiRequest = kwargs[request_param_name]
620
630
  if request_param_name == _CADWYN_REQUEST_PARAM_NAME:
@@ -655,6 +665,7 @@ class VersionBundle:
655
665
  api_version,
656
666
  head_route,
657
667
  exit_stack=exit_stack,
668
+ embed_body_fields=embed_body_fields,
658
669
  )
659
670
  # Because we re-added it into our kwargs when we did solve_dependencies
660
671
  if _CADWYN_REQUEST_PARAM_NAME in new_kwargs:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cadwyn"
3
- version = "4.2.1"
3
+ version = "4.2.3"
4
4
  description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
5
5
  authors = ["Stanislav Zmiev <zmievsa@gmail.com>"]
6
6
  license = "MIT"
@@ -50,19 +50,16 @@ classifiers = [
50
50
 
51
51
 
52
52
  [tool.poetry.dependencies]
53
+ fastapi = { version = ">=0.112.3", extras = ["standard"] }
53
54
  python = "^3.10"
54
55
  typing-extensions = "*"
55
- fastapi = ">=0.110.0"
56
- starlette = ">=0.30.0" # Because of this: https://github.com/encode/starlette/pull/2191
56
+ starlette = ">=0.30.0"
57
57
  pydantic = ">=2.0.0"
58
- typer = { version = ">=0.7.0", optional = true }
58
+ typer = ">=0.7.0"
59
59
  jinja2 = ">=3.1.2"
60
60
  issubclass = "^0.1.2"
61
61
  backports-strenum = { version = "^1.3.1", python = "<3.11" }
62
62
 
63
- [tool.poetry.extras]
64
- cli = ["typer"]
65
-
66
63
 
67
64
  [tool.poetry.group.dev.dependencies]
68
65
  pytest = ">=7.2.1"
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