cadwyn 3.15.6__tar.gz → 3.15.8__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 (37) hide show
  1. {cadwyn-3.15.6 → cadwyn-3.15.8}/PKG-INFO +1 -1
  2. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/_asts.py +0 -1
  3. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_common.py +2 -1
  4. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/route_generation.py +83 -38
  5. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/modules.py +2 -1
  6. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/versions.py +4 -1
  7. {cadwyn-3.15.6 → cadwyn-3.15.8}/pyproject.toml +1 -1
  8. {cadwyn-3.15.6 → cadwyn-3.15.8}/LICENSE +0 -0
  9. {cadwyn-3.15.6 → cadwyn-3.15.8}/README.md +0 -0
  10. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/__init__.py +0 -0
  11. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/__main__.py +0 -0
  12. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/_compat.py +0 -0
  13. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/_package_utils.py +0 -0
  14. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/_utils.py +0 -0
  15. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/applications.py +0 -0
  16. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/README.md +0 -0
  17. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/__init__.py +0 -0
  18. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_main.py +0 -0
  19. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/__init__.py +0 -0
  20. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/class_migrations.py +0 -0
  21. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/class_rebuilding.py +0 -0
  22. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/class_renaming.py +0 -0
  23. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/import_auto_adding.py +0 -0
  24. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/codegen/_plugins/module_migrations.py +0 -0
  25. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/exceptions.py +0 -0
  26. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/main.py +0 -0
  27. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/middleware.py +0 -0
  28. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/py.typed +0 -0
  29. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/routing.py +0 -0
  30. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/static/__init__.py +0 -0
  31. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/static/docs.html +0 -0
  32. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/__init__.py +0 -0
  33. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/common.py +0 -0
  34. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/data.py +0 -0
  35. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/endpoints.py +0 -0
  36. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/enums.py +0 -0
  37. {cadwyn-3.15.6 → cadwyn-3.15.8}/cadwyn/structure/schemas.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 3.15.6
3
+ Version: 3.15.8
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
@@ -156,7 +156,6 @@ def transform_other(value: Any) -> Any:
156
156
  def _get_lambda_source_from_default_factory(source: str) -> str:
157
157
  found_lambdas: list[ast.Lambda] = []
158
158
 
159
- ast.parse(source)
160
159
  for node in ast.walk(ast.parse(source)):
161
160
  if isinstance(node, ast.keyword) and node.arg == "default_factory" and isinstance(node.value, ast.Lambda):
162
161
  found_lambdas.append(node.value)
@@ -1,6 +1,7 @@
1
1
  import ast
2
2
  import dataclasses
3
3
  import inspect
4
+ import textwrap
4
5
  from dataclasses import dataclass
5
6
  from enum import Enum
6
7
  from functools import cache
@@ -89,7 +90,7 @@ def get_fields_and_validators_from_model(
89
90
  {},
90
91
  )
91
92
  else:
92
- cls_ast = cast(ast.ClassDef, ast.parse(source).body[0])
93
+ cls_ast = cast(ast.ClassDef, ast.parse(textwrap.dedent(source)).body[0])
93
94
  validators: dict[str, _ValidatorWrapper] = {}
94
95
 
95
96
  validators_and_nones = (
@@ -29,11 +29,7 @@ import fastapi.params
29
29
  import fastapi.routing
30
30
  import fastapi.security.base
31
31
  import fastapi.utils
32
- from fastapi import (
33
- APIRouter,
34
- Request, # noqa: F401 # We import Request for libraries like svcs that expect it to be in globals
35
- Response, # noqa: F401 # We import Request for libraries like svcs that expect it to be in globals
36
- )
32
+ from fastapi import APIRouter
37
33
  from fastapi._compat import ModelField as FastAPIModelField
38
34
  from fastapi._compat import create_body_model
39
35
  from fastapi.params import Depends
@@ -67,7 +63,7 @@ from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPON
67
63
  if TYPE_CHECKING:
68
64
  from fastapi.dependencies.models import Dependant
69
65
 
70
- _T = TypeVar("_T", bound=Callable[..., Any])
66
+ _Call = TypeVar("_Call", bound=Callable[..., Any])
71
67
  _R = TypeVar("_R", bound=fastapi.routing.APIRouter)
72
68
  # This is a hack we do because we can't guarantee how the user will use the router.
73
69
  _DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
@@ -121,7 +117,7 @@ def generate_versioned_routers(
121
117
 
122
118
 
123
119
  class VersionedAPIRouter(fastapi.routing.APIRouter):
124
- def only_exists_in_older_versions(self, endpoint: _T) -> _T:
120
+ def only_exists_in_older_versions(self, endpoint: _Call) -> _Call:
125
121
  route = _get_route_from_func(self.routes, endpoint)
126
122
  if route is None:
127
123
  raise LookupError(
@@ -466,9 +462,10 @@ def _extract_internal_request_schemas_from_router(
466
462
 
467
463
  for route in router.routes:
468
464
  if isinstance(route, APIRoute): # pragma: no branch
469
- route.endpoint = _modify_callable(
465
+ route.endpoint = _modify_callable_annotations(
470
466
  route.endpoint,
471
467
  modify_annotations=_extract_internal_request_schemas_from_annotations,
468
+ annotation_modifying_wrapper_factory=_copy_endpoint,
472
469
  )
473
470
  _remake_endpoint_dependencies(route)
474
471
  return schema_to_internal_request_body_representation
@@ -575,7 +572,12 @@ class _AnnotationTransformer:
575
572
  def modifier(annotation: Any):
576
573
  return self._change_version_of_annotations(annotation, version_dir)
577
574
 
578
- return _modify_callable(annotation, modifier, modifier)
575
+ return _modify_callable_annotations(
576
+ annotation,
577
+ modifier,
578
+ modifier,
579
+ annotation_modifying_wrapper_factory=_copy_function_through_class_based_wrapper,
580
+ )
579
581
  else:
580
582
  return annotation
581
583
 
@@ -636,12 +638,14 @@ class _AnnotationTransformer:
636
638
  )
637
639
 
638
640
 
639
- def _modify_callable(
640
- call: Callable,
641
+ def _modify_callable_annotations(
642
+ call: _Call,
641
643
  modify_annotations: Callable[[dict[str, Any]], dict[str, Any]] = lambda a: a,
642
644
  modify_defaults: Callable[[tuple[Any, ...]], tuple[Any, ...]] = lambda a: a,
643
- ):
644
- annotation_modifying_wrapper = _copy_function(call)
645
+ *,
646
+ annotation_modifying_wrapper_factory: Callable[[_Call], _Call],
647
+ ) -> _Call:
648
+ annotation_modifying_wrapper = annotation_modifying_wrapper_factory(call)
645
649
  old_params = inspect.signature(call).parameters
646
650
  callable_annotations = annotation_modifying_wrapper.__annotations__
647
651
  annotation_modifying_wrapper.__annotations__ = modify_annotations(callable_annotations)
@@ -798,37 +802,78 @@ def _get_route_from_func(
798
802
  return None
799
803
 
800
804
 
801
- def _copy_function(function: _T) -> _T:
802
- while hasattr(function, "__alt_wrapped__"):
803
- function = function.__alt_wrapped__
804
- if not isinstance(function, types.FunctionType | types.MethodType):
805
- # This means that the callable is actually an instance of a regular class
806
- function = function.__call__
807
- if inspect.iscoroutinefunction(function):
805
+ def _copy_endpoint(function: Any) -> Any:
806
+ function = _unwrap_callable(function)
807
+ function_copy: Any = types.FunctionType(
808
+ function.__code__,
809
+ function.__globals__,
810
+ name=function.__name__,
811
+ argdefs=function.__defaults__,
812
+ closure=function.__closure__,
813
+ )
814
+ function_copy = functools.update_wrapper(function_copy, function)
815
+ # Otherwise it will have the same signature as __wrapped__ due to how inspect module works
816
+ del function_copy.__wrapped__
817
+
818
+ function_copy._original_callable = function
819
+ function.__kwdefaults__ = function.__kwdefaults__.copy() if function.__kwdefaults__ is not None else {}
820
+
821
+ return function_copy
808
822
 
809
- @functools.wraps(function)
810
- async def annotation_modifying_wrapper( # pyright: ignore[reportRedeclaration]
811
- *args: Any,
812
- **kwargs: Any,
813
- ) -> Any:
814
- return await function(*args, **kwargs)
815
823
 
824
+ class _CallableWrapper:
825
+ """__eq__ and __hash__ are needed to make sure that dependency overrides work correctly.
826
+ They are based on putting dependencies (functions) as keys for the dictionary so if we want to be able to
827
+ override the wrapper, we need to make sure that it is equivalent to the original in __hash__ and __eq__
828
+ """
829
+
830
+ def __init__(self, original_callable: Callable) -> None:
831
+ super().__init__()
832
+ self._original_callable = original_callable
833
+ functools.update_wrapper(self, original_callable)
834
+
835
+ @property
836
+ def __globals__(self):
837
+ """FastAPI uses __globals__ to resolve forward references in type hints
838
+ It's supposed to be an attribute on the function but we use it as property to prevent python
839
+ from trying to pickle globals when we deepcopy this wrapper
840
+ """
841
+ #
842
+ return self._original_callable.__globals__
843
+
844
+ def __call__(self, *args: Any, **kwargs: Any):
845
+ return self._original_callable(*args, **kwargs)
846
+
847
+ def __hash__(self):
848
+ return hash(self._original_callable)
849
+
850
+ def __eq__(self, value: object) -> bool:
851
+ return self._original_callable == value # pyright: ignore[reportUnnecessaryComparison]
852
+
853
+
854
+ class _AsyncCallableWrapper(_CallableWrapper):
855
+ async def __call__(self, *args: Any, **kwargs: Any):
856
+ return await self._original_callable(*args, **kwargs)
857
+
858
+
859
+ def _copy_function_through_class_based_wrapper(call: Any):
860
+ """Separate from copy_endpoint because endpoints MUST be functions in FastAPI, they cannot be cls instances"""
861
+ call = _unwrap_callable(call)
862
+
863
+ if inspect.iscoroutinefunction(call):
864
+ return _AsyncCallableWrapper(call)
816
865
  else:
866
+ return _CallableWrapper(call)
817
867
 
818
- @functools.wraps(function)
819
- def annotation_modifying_wrapper(
820
- *args: Any,
821
- **kwargs: Any,
822
- ) -> Any:
823
- return function(*args, **kwargs)
824
868
 
825
- # Otherwise it will have the same signature as __wrapped__ due to how inspect module works
826
- annotation_modifying_wrapper.__alt_wrapped__ = ( # pyright: ignore[reportAttributeAccessIssue]
827
- annotation_modifying_wrapper.__wrapped__
828
- )
829
- del annotation_modifying_wrapper.__wrapped__
869
+ def _unwrap_callable(call: Any) -> Any:
870
+ while hasattr(call, "_original_callable"):
871
+ call = call._original_callable
872
+ if not isinstance(call, types.FunctionType | types.MethodType):
873
+ # This means that the callable is actually an instance of a regular class
874
+ call = call.__call__
830
875
 
831
- return cast(_T, annotation_modifying_wrapper)
876
+ return call
832
877
 
833
878
 
834
879
  def _route_has_a_simple_body_schema(route: APIRoute) -> bool:
@@ -1,5 +1,6 @@
1
1
  import ast
2
2
  import dataclasses
3
+ import textwrap
3
4
  from dataclasses import InitVar, dataclass
4
5
  from types import ModuleType
5
6
 
@@ -13,7 +14,7 @@ class AlterModuleInstruction:
13
14
  import_: ast.Import | ast.ImportFrom = dataclasses.field(init=False)
14
15
 
15
16
  def __post_init__(self, raw_import: str):
16
- parsed_body = ast.parse(raw_import).body
17
+ parsed_body = ast.parse(textwrap.dedent(raw_import)).body
17
18
  if len(parsed_body) > 1:
18
19
  raise CadwynStructureError(
19
20
  f"You have specified more than just a single import. This is prohibited. "
@@ -671,7 +671,10 @@ class VersionBundle:
671
671
  # that do not have it. We don't support it too.
672
672
  if response_info.body is not None and hasattr(response_info._response, "body"):
673
673
  # TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
674
- if isinstance(response_info.body, str):
674
+ if (
675
+ isinstance(response_info.body, str)
676
+ and response_info._response.headers.get("content-type") != "application/json"
677
+ ):
675
678
  response_info._response.body = response_info.body.encode(response_info._response.charset)
676
679
  else:
677
680
  response_info._response.body = json.dumps(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cadwyn"
3
- version = "3.15.6"
3
+ version = "3.15.8"
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"
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