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.
- {cadwyn-4.2.1 → cadwyn-4.2.3}/PKG-INFO +3 -4
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_asts.py +2 -2
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/applications.py +9 -1
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/middleware.py +4 -4
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/schema_generation.py +3 -3
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/versions.py +19 -8
- {cadwyn-4.2.1 → cadwyn-4.2.3}/pyproject.toml +4 -7
- {cadwyn-4.2.1 → cadwyn-4.2.3}/LICENSE +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/README.md +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/__init__.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/__main__.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_importer.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_render.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/_utils.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/changelogs.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/exceptions.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/py.typed +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/route_generation.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/routing.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/static/__init__.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/static/docs.html +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/common.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/data.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-4.2.1 → cadwyn-4.2.3}/cadwyn/structure/enums.py +0 -0
- {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.
|
|
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.
|
|
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)
|
|
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(_:
|
|
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 =
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|