cadwyn 2.2.0__py3-none-any.whl → 2.3.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,14 +1,14 @@
1
1
  import importlib.metadata
2
2
 
3
3
  from .codegen import generate_code_for_versioned_packages
4
- from .header import get_cadwyn_dependency
4
+ from .main import Cadwyn
5
5
  from .routing import VersionedAPIRouter, generate_versioned_routers
6
6
  from .structure import VersionBundle, internal_body_representation_of
7
7
 
8
8
  __version__ = importlib.metadata.version("cadwyn")
9
9
  __all__ = [
10
+ "Cadwyn",
10
11
  "VersionedAPIRouter",
11
- "get_cadwyn_dependency",
12
12
  "generate_code_for_versioned_packages",
13
13
  "VersionBundle",
14
14
  "generate_versioned_routers",
cadwyn/codegen.py CHANGED
@@ -15,8 +15,8 @@ from types import GenericAlias, LambdaType, ModuleType, NoneType
15
15
  from typing import (
16
16
  Any,
17
17
  TypeAlias,
18
- _BaseGenericAlias,
19
- cast, # pyright: ignore[reportGeneralTypeIssues]
18
+ _BaseGenericAlias, # pyright: ignore[reportGeneralTypeIssues]
19
+ cast,
20
20
  final,
21
21
  get_args,
22
22
  get_origin,
cadwyn/exceptions.py CHANGED
@@ -1,4 +1,17 @@
1
1
  from fastapi.routing import APIRoute
2
+ from verselect.exceptions import AppCreationError
3
+
4
+ __all__ = [
5
+ "AppCreationError",
6
+ "CadwynError",
7
+ "LintingError",
8
+ "CodeGenerationError",
9
+ "InvalidGenerationInstructionError",
10
+ "RouterGenerationError",
11
+ "RouteAlreadyExistsError",
12
+ "CadwynStructureError",
13
+ "ModuleIsNotVersionedError",
14
+ ]
2
15
 
3
16
 
4
17
  class CadwynError(Exception):
cadwyn/main.py CHANGED
@@ -6,29 +6,26 @@ from fastapi import APIRouter, routing
6
6
  from fastapi.datastructures import Default
7
7
  from fastapi.params import Depends
8
8
  from fastapi.utils import generate_unique_id
9
- from fastapi_header_versioning import HeaderRoutingFastAPI
10
- from fastapi_header_versioning.fastapi import HeaderVersionedAPIRouter
11
9
  from starlette.middleware import Middleware
12
10
  from starlette.requests import Request
13
11
  from starlette.responses import JSONResponse, Response
14
12
  from starlette.routing import BaseRoute
15
13
  from starlette.types import Lifespan
16
14
  from typing_extensions import Self
15
+ from verselect import HeaderRoutingFastAPI
17
16
 
18
- from cadwyn.header import get_cadwyn_dependency
19
17
  from cadwyn.routing import generate_versioned_routers
20
18
  from cadwyn.structure import VersionBundle
21
19
 
22
20
 
23
- class _Cadwyn(HeaderRoutingFastAPI):
21
+ class Cadwyn(HeaderRoutingFastAPI):
24
22
  def __init__(
25
23
  self,
26
24
  *,
27
25
  versions: VersionBundle,
28
26
  latest_schemas_module: ModuleType,
29
- version_header: str = "x-api-version",
27
+ api_version_header_name: str = "x-api-version",
30
28
  debug: bool = False,
31
- routes: list[BaseRoute] | None = None,
32
29
  title: str = "FastAPI",
33
30
  summary: str | None = None,
34
31
  description: str = "",
@@ -40,7 +37,7 @@ class _Cadwyn(HeaderRoutingFastAPI):
40
37
  default_response_class: type[Response] = Default(JSONResponse), # noqa: B008
41
38
  redirect_slashes: bool = True,
42
39
  docs_url: str | None = "/docs",
43
- redoc_url: str | None = "/redoc",
40
+ redoc_url: None = None,
44
41
  swagger_ui_oauth2_redirect_url: str | None = "/docs/oauth2-redirect",
45
42
  swagger_ui_init_oauth: dict[str, Any] | None = None,
46
43
  middleware: Sequence[Middleware] | None = None,
@@ -66,9 +63,9 @@ class _Cadwyn(HeaderRoutingFastAPI):
66
63
  **extra: Any,
67
64
  ) -> None:
68
65
  super().__init__(
69
- version_header=version_header,
66
+ api_version_header_name=api_version_header_name,
67
+ api_version_var=versions.api_version_var,
70
68
  debug=debug,
71
- routes=routes,
72
69
  title=title,
73
70
  summary=summary,
74
71
  description=description,
@@ -80,7 +77,6 @@ class _Cadwyn(HeaderRoutingFastAPI):
80
77
  default_response_class=default_response_class,
81
78
  redirect_slashes=redirect_slashes,
82
79
  docs_url=docs_url,
83
- redoc_url=redoc_url,
84
80
  swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url,
85
81
  swagger_ui_init_oauth=swagger_ui_init_oauth,
86
82
  middleware=middleware,
@@ -106,21 +102,15 @@ class _Cadwyn(HeaderRoutingFastAPI):
106
102
  )
107
103
  self.versions = versions
108
104
  self.latest_schemas_module = latest_schemas_module
109
- self.version_header = version_header
110
- self.cadwyn_header_dependency = get_cadwyn_dependency(
111
- version_header_name=version_header,
112
- api_version_var=self.versions.api_version_var,
113
- )
114
105
 
115
- def include_versioned_routers(self, *routers: APIRouter) -> None:
106
+ def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
107
+ root_router = APIRouter()
108
+ for router in routers:
109
+ root_router.include_router(router)
116
110
  router_versions = generate_versioned_routers(
117
- *routers,
111
+ root_router,
118
112
  versions=self.versions,
119
113
  latest_schemas_module=self.latest_schemas_module,
120
114
  )
121
- root_router = HeaderVersionedAPIRouter()
122
-
123
115
  for version, router in router_versions.items():
124
- root_router.include_router(router, version=str(version))
125
-
126
- self.include_router(root_router, dependencies=[self.cadwyn_header_dependency])
116
+ self.add_header_versioned_routers(router, header_value=version.isoformat())
cadwyn/routing.py CHANGED
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
  from types import GenericAlias, MappingProxyType, ModuleType
13
13
  from typing import (
14
14
  Any,
15
+ Generic,
15
16
  TypeAlias,
16
17
  TypeVar,
17
18
  _BaseGenericAlias, # pyright: ignore[reportGeneralTypeIssues]
@@ -40,6 +41,7 @@ from starlette.routing import (
40
41
  request_response,
41
42
  )
42
43
  from typing_extensions import assert_never
44
+ from verselect.routing import VERSION_HEADER_FORMAT
43
45
 
44
46
  from cadwyn._utils import Sentinel, UnionType, get_another_version_of_module
45
47
  from cadwyn.codegen import _get_package_path_from_module, _get_version_dir_path
@@ -54,12 +56,18 @@ from cadwyn.structure.endpoints import (
54
56
  )
55
57
  from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPONSE_PARAM_NAME, VersionChange
56
58
 
59
+ __all__ = [
60
+ "generate_versioned_routers",
61
+ "VersionedAPIRouter",
62
+ "VERSION_HEADER_FORMAT",
63
+ ]
64
+
57
65
  _T = TypeVar("_T", bound=Callable[..., Any])
58
66
  _R = TypeVar("_R", bound=fastapi.routing.APIRouter)
59
67
  # This is a hack we do because we can't guarantee how the user will use the router.
60
68
  _DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
61
- EndpointPath: TypeAlias = str
62
- EndpointMethod: TypeAlias = str
69
+ _EndpointPath: TypeAlias = str
70
+ _EndpointMethod: TypeAlias = str
63
71
 
64
72
 
65
73
  @dataclass(slots=True, frozen=True, eq=True)
@@ -69,9 +77,9 @@ class _EndpointInfo:
69
77
 
70
78
 
71
79
  @dataclass(slots=True)
72
- class _RouterInfo:
73
- router: fastapi.routing.APIRouter
74
- routes_with_migrated_requests: dict[EndpointPath, set[EndpointMethod]]
80
+ class _RouterInfo(Generic[_R]):
81
+ router: _R
82
+ routes_with_migrated_requests: dict[_EndpointPath, set[_EndpointMethod]]
75
83
  route_bodies_with_migrated_requests: set[type[BaseModel]]
76
84
 
77
85
 
@@ -97,14 +105,14 @@ class VersionedAPIRouter(fastapi.routing.APIRouter):
97
105
  return endpoint
98
106
 
99
107
 
100
- @final
101
- class _EndpointTransformer:
108
+ class _EndpointTransformer(Generic[_R]):
102
109
  def __init__(
103
110
  self,
104
- parent_router: fastapi.routing.APIRouter,
111
+ parent_router: _R,
105
112
  versions: VersionBundle,
106
113
  latest_schemas_module: ModuleType,
107
114
  ) -> None:
115
+ super().__init__()
108
116
  self.parent_router = parent_router
109
117
  self.versions = versions
110
118
  self.annotation_transformer = _AnnotationTransformer(latest_schemas_module, versions)
@@ -113,7 +121,7 @@ class _EndpointTransformer:
113
121
  route for route in parent_router.routes if isinstance(route, APIRoute) and _DELETED_ROUTE_TAG in route.tags
114
122
  ]
115
123
 
116
- def transform(self):
124
+ def transform(self) -> dict[VersionDate, _R]:
117
125
  router = deepcopy(self.parent_router)
118
126
  router_infos: dict[VersionDate, _RouterInfo] = {}
119
127
  routes_with_migrated_requests = {}
@@ -446,41 +454,23 @@ class _AnnotationTransformer:
446
454
  return self._change_version_of_type(annotation, version_dir)
447
455
  elif callable(annotation):
448
456
  # TASK: https://github.com/zmievsa/cadwyn/issues/48
449
- if inspect.iscoroutinefunction(annotation):
450
-
451
- @functools.wraps(annotation)
452
- async def new_callable( # pyright: ignore[reportGeneralTypeIssues]
453
- *args: Any,
454
- **kwargs: Any,
455
- ) -> Any:
456
- return await annotation(*args, **kwargs)
457
-
458
- else:
459
-
460
- @functools.wraps(annotation)
461
- def new_callable( # pyright: ignore[reportGeneralTypeIssues]
462
- *args: Any,
463
- **kwargs: Any,
464
- ) -> Any:
465
- return annotation(*args, **kwargs)
466
-
467
- # Otherwise it will have the same signature as __wrapped__
468
- new_callable.__alt_wrapped__ = new_callable.__wrapped__ # pyright: ignore[reportGeneralTypeIssues]
469
- del new_callable.__wrapped__
457
+ annotation_modifying_decorator = _copy_function(annotation)
470
458
  old_params = inspect.signature(annotation).parameters
471
- callable_annotations = new_callable.__annotations__
459
+ callable_annotations = annotation_modifying_decorator.__annotations__
472
460
 
473
- new_callable: Any = cast(Any, new_callable)
474
- new_callable.__annotations__ = self._change_version_of_annotations(
461
+ annotation_modifying_decorator.__annotations__ = self._change_version_of_annotations(
475
462
  callable_annotations,
476
463
  version_dir,
477
464
  )
478
- new_callable.__defaults__ = self._change_version_of_annotations(
465
+ annotation_modifying_decorator.__defaults__ = self._change_version_of_annotations(
479
466
  tuple(p.default for p in old_params.values() if p.default is not inspect.Signature.empty),
480
467
  version_dir,
481
468
  )
482
- new_callable.__signature__ = _generate_signature(new_callable, old_params)
483
- return new_callable
469
+ annotation_modifying_decorator.__signature__ = _generate_signature(
470
+ annotation_modifying_decorator,
471
+ old_params,
472
+ )
473
+ return annotation_modifying_decorator
484
474
  else:
485
475
  return annotation
486
476
 
@@ -581,7 +571,7 @@ def _add_data_migrations_to_route(
581
571
  route.endpoint = versions._versioned(
582
572
  template_body_field,
583
573
  template_body_field_name,
584
- route.dependant.body_params,
574
+ route,
585
575
  dependant_for_request_migrations,
586
576
  latest_response_model,
587
577
  request_param_name=route.dependant.request_param_name,
@@ -672,7 +662,7 @@ def _get_route_from_func(
672
662
  return None
673
663
 
674
664
 
675
- def _get_migrated_routes_by_path(version: Version) -> dict[EndpointPath, set[EndpointMethod]]:
665
+ def _get_migrated_routes_by_path(version: Version) -> dict[_EndpointPath, set[_EndpointMethod]]:
676
666
  request_by_path_migration_instructions = [
677
667
  version_change.alter_request_by_path_instructions for version_change in version.version_changes
678
668
  ]
@@ -682,3 +672,34 @@ def _get_migrated_routes_by_path(version: Version) -> dict[EndpointPath, set[End
682
672
  for instruction in instruction_list:
683
673
  migrated_routes[path] |= instruction.methods
684
674
  return migrated_routes
675
+
676
+
677
+ def _copy_function(function: _T) -> _T:
678
+ while hasattr(function, "__alt_wrapped__"):
679
+ function = function.__alt_wrapped__
680
+
681
+ if inspect.iscoroutinefunction(function):
682
+
683
+ @functools.wraps(function)
684
+ async def annotation_modifying_decorator( # pyright: ignore[reportGeneralTypeIssues]
685
+ *args: Any,
686
+ **kwargs: Any,
687
+ ) -> Any:
688
+ return await function(*args, **kwargs)
689
+
690
+ else:
691
+
692
+ @functools.wraps(function)
693
+ def annotation_modifying_decorator(
694
+ *args: Any,
695
+ **kwargs: Any,
696
+ ) -> Any:
697
+ return function(*args, **kwargs)
698
+
699
+ # Otherwise it will have the same signature as __wrapped__ due to how inspect module works
700
+ annotation_modifying_decorator.__alt_wrapped__ = ( # pyright: ignore[reportGeneralTypeIssues]
701
+ annotation_modifying_decorator.__wrapped__
702
+ )
703
+ del annotation_modifying_decorator.__wrapped__
704
+
705
+ return cast(_T, annotation_modifying_decorator)
@@ -1,21 +1,24 @@
1
+ import email.message
1
2
  import functools
2
3
  import inspect
3
4
  import json
4
5
  from collections import defaultdict
5
6
  from collections.abc import Callable, Sequence
7
+ from contextlib import AsyncExitStack
6
8
  from contextvars import ContextVar
7
9
  from enum import Enum
8
- from typing import Any, ClassVar, ParamSpec, TypeAlias, TypeVar
10
+ from typing import Any, ClassVar, ParamSpec, TypeAlias, TypeVar, cast
9
11
 
12
+ from fastapi import HTTPException, params
10
13
  from fastapi import Request as FastapiRequest
11
14
  from fastapi import Response as FastapiResponse
12
- from fastapi import params
13
15
  from fastapi._compat import ModelField, _normalize_errors
14
16
  from fastapi.dependencies.models import Dependant
15
17
  from fastapi.dependencies.utils import solve_dependencies
16
18
  from fastapi.exceptions import RequestValidationError
17
- from fastapi.routing import _prepare_response_content
19
+ from fastapi.routing import APIRoute, _prepare_response_content
18
20
  from pydantic import BaseModel
21
+ from pydantic.fields import Undefined
19
22
  from typing_extensions import assert_never
20
23
 
21
24
  from cadwyn.exceptions import CadwynError, CadwynStructureError
@@ -45,6 +48,7 @@ PossibleInstructions: TypeAlias = (
45
48
  | AlterSchemaInstruction
46
49
  | staticmethod
47
50
  )
51
+ APIVersionVarType: TypeAlias = ContextVar[VersionDate | None] | ContextVar[VersionDate]
48
52
 
49
53
 
50
54
  class VersionChange:
@@ -203,25 +207,27 @@ class Version:
203
207
  class VersionBundle:
204
208
  def __init__(
205
209
  self,
206
- *versions: Version,
207
- api_version_var: ContextVar[VersionDate | None] | ContextVar[VersionDate],
210
+ latest_version: Version,
211
+ /,
212
+ *other_versions: Version,
213
+ api_version_var: APIVersionVarType,
208
214
  ) -> None:
209
215
  super().__init__()
210
216
 
211
- self.versions = versions
217
+ self.versions = (latest_version, *other_versions)
212
218
  self.api_version_var = api_version_var
213
- if sorted(versions, key=lambda v: v.value, reverse=True) != list(versions):
214
- raise ValueError(
219
+ if sorted(self.versions, key=lambda v: v.value, reverse=True) != list(self.versions):
220
+ raise CadwynStructureError(
215
221
  "Versions are not sorted correctly. Please sort them in descending order.",
216
222
  )
217
- if versions[-1].version_changes:
223
+ if self.versions[-1].version_changes:
218
224
  raise CadwynStructureError(
219
- f'The first version "{versions[-1].value}" cannot have any version changes. '
225
+ f'The first version "{self.versions[-1].value}" cannot have any version changes. '
220
226
  "Version changes are defined to migrate to/from a previous version so you "
221
227
  "cannot define one for the very first version.",
222
228
  )
223
229
  version_values = set()
224
- for version in versions:
230
+ for version in self.versions:
225
231
  if version.value not in version_values:
226
232
  version_values.add(version.value)
227
233
  else:
@@ -293,6 +299,7 @@ class VersionBundle:
293
299
  request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
294
300
  del request._headers
295
301
  # Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
302
+
296
303
  new_kwargs, errors, _, _, _ = await solve_dependencies(
297
304
  request=request,
298
305
  response=response,
@@ -301,7 +308,6 @@ class VersionBundle:
301
308
  # TODO: Take it from route
302
309
  dependency_overrides_provider=None,
303
310
  )
304
-
305
311
  if errors:
306
312
  raise RequestValidationError(_normalize_errors(errors), body=request_info.body)
307
313
  return new_kwargs
@@ -346,7 +352,7 @@ class VersionBundle:
346
352
  self,
347
353
  template_module_body_field_for_request_migrations: type[BaseModel] | None,
348
354
  module_body_field_name: str | None,
349
- body_params: list[ModelField],
355
+ route: APIRoute,
350
356
  dependant_for_request_migrations: Dependant,
351
357
  latest_response_model: Any,
352
358
  *,
@@ -368,7 +374,7 @@ class VersionBundle:
368
374
  request_param_name,
369
375
  kwargs,
370
376
  response,
371
- body_params,
377
+ route,
372
378
  )
373
379
 
374
380
  return await self._convert_endpoint_response_to_version(
@@ -445,9 +451,8 @@ class VersionBundle:
445
451
  request_param_name: str,
446
452
  kwargs: dict[str, Any],
447
453
  response: FastapiResponse,
448
- body_params: list[ModelField],
454
+ route: APIRoute,
449
455
  ):
450
- is_single_body_field = len(body_params) == 1
451
456
  request: FastapiRequest = kwargs[request_param_name]
452
457
  if request_param_name == _CADWYN_REQUEST_PARAM_NAME:
453
458
  kwargs.pop(request_param_name)
@@ -456,14 +461,13 @@ class VersionBundle:
456
461
  if api_version is None:
457
462
  return kwargs
458
463
 
464
+ # TODO: What if the user never edits it? We just add a round of (de)serialization
459
465
  if (
460
- is_single_body_field
466
+ len(route.dependant.body_params) == 1
461
467
  and template_module_body_field_for_request_migrations is not None
462
468
  and body_field_alias is not None
463
469
  and body_field_alias in kwargs
464
470
  ):
465
- # TODO: What if the user never edits it? We just add a round of (de)serialization
466
-
467
471
  raw_body = kwargs[body_field_alias]
468
472
  if raw_body is None:
469
473
  body = None
@@ -471,12 +475,8 @@ class VersionBundle:
471
475
  body = raw_body.dict(by_alias=True, exclude_unset=True)
472
476
  if kwargs[body_field_alias].__custom_root_type__:
473
477
  body = body["__root__"]
474
- # TODO: What if it's large? We need to also make ours a generator, then... But we can't because ours is
475
- # synchronous. HMM... Or maybe just reading it later will solve the issue. Who knows...
476
- elif any(isinstance(param.field_info, params.Form) for param in body_params):
477
- body = await request.form()
478
478
  else:
479
- body = await request.body()
479
+ body = await _get_body(request, route.body_field)
480
480
  request_info = RequestInfo(request, body)
481
481
  new_kwargs = await self._migrate_request(
482
482
  template_module_body_field_for_request_migrations,
@@ -493,6 +493,53 @@ class VersionBundle:
493
493
  return new_kwargs
494
494
 
495
495
 
496
+ async def _get_body(request: FastapiRequest, body_field: ModelField | None): # pragma: no cover # This is from fastapi
497
+ is_body_form = body_field and isinstance(body_field.field_info, params.Form)
498
+ try:
499
+ body: Any = None
500
+ if body_field:
501
+ if is_body_form:
502
+ body = await request.form()
503
+ stack = cast(AsyncExitStack, request.scope.get("fastapi_astack"))
504
+ stack.push_async_callback(body.close)
505
+ else:
506
+ body_bytes = await request.body()
507
+ if body_bytes:
508
+ json_body: Any = Undefined
509
+ content_type_value = request.headers.get("content-type")
510
+ if not content_type_value:
511
+ json_body = await request.json()
512
+ else:
513
+ message = email.message.Message()
514
+ message["content-type"] = content_type_value
515
+ if message.get_content_maintype() == "application":
516
+ subtype = message.get_content_subtype()
517
+ if subtype == "json" or subtype.endswith("+json"):
518
+ json_body = await request.json()
519
+ if json_body != Undefined:
520
+ body = json_body
521
+ else:
522
+ body = body_bytes
523
+ except json.JSONDecodeError as e:
524
+ raise RequestValidationError(
525
+ [
526
+ {
527
+ "type": "json_invalid",
528
+ "loc": ("body", e.pos),
529
+ "msg": "JSON decode error",
530
+ "input": {},
531
+ "ctx": {"error": e.msg},
532
+ },
533
+ ],
534
+ body=e.doc,
535
+ ) from e
536
+ except HTTPException:
537
+ raise
538
+ except Exception as e: # noqa: BLE001
539
+ raise HTTPException(status_code=400, detail="There was an error parsing the body") from e
540
+ return body
541
+
542
+
496
543
  def _add_keyword_only_parameter(
497
544
  func: Callable,
498
545
  param_name: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Modern Stripe-like API versioning in FastAPI
5
5
  Home-page: https://github.com/zmievsa/cadwyn
6
6
  License: MIT
@@ -32,10 +32,10 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
32
  Classifier: Typing :: Typed
33
33
  Provides-Extra: cli
34
34
  Requires-Dist: fastapi (>=0.96.1)
35
- Requires-Dist: fastapi-header-versioning (>=1.1.0)
36
35
  Requires-Dist: pydantic (>=1.10.0,<2.0.0)
37
36
  Requires-Dist: typer (>=0.7.0); extra == "cli"
38
37
  Requires-Dist: typing-extensions
38
+ Requires-Dist: verselect-zmievsa (>=0.0.5)
39
39
  Project-URL: Repository, https://github.com/zmievsa/cadwyn
40
40
  Description-Content-Type: text/markdown
41
41
 
@@ -73,10 +73,10 @@ Its [approach](./docs/theory.md#ii-migration-based-response-building) will be us
73
73
 
74
74
  The [documentation](https://docs.cadwyn.dev) has everything you need to get started. It is recommended to read it in the following order:
75
75
 
76
- 1. [Tutorial](https://docs.cadwyn.dev/tutorial/)
77
- 2. [Recipes](https://docs.cadwyn.dev/recipes/)
78
- 3. [Reference](https://docs.cadwyn.dev/reference/)
79
- 4. [Theory](https://docs.cadwyn.dev/theory/)
76
+ 1. [Tutorial](./tutorial.md)
77
+ 2. [Recipes](./recipes.md)
78
+ 3. [Reference](./reference.md)
79
+ 4. [Theory](./theory.md) <!-- TODO: Move section about cadwyn's approach to the beginning and move other approaches and "how we got here" to another article -->
80
80
 
81
81
  ## Similar projects
82
82
 
@@ -0,0 +1,20 @@
1
+ cadwyn/__init__.py,sha256=95dxX1XyN-i4pPn1Ji1E5TMnWe6olqm1MYutMiWTtWw,492
2
+ cadwyn/__main__.py,sha256=aKAwxVnhqi3ATd1UsifoLA1t3udTzz56t0BRTlktX1A,2845
3
+ cadwyn/_utils.py,sha256=Uh6No0FNY1AR2Z2E19fMPIU9_J4lbuG8XOQU2AlDIZw,3600
4
+ cadwyn/codegen.py,sha256=oSivxLD_DmWbO179a9n4gHIoxuBJqDhP1OapjXv9DmE,38212
5
+ cadwyn/exceptions.py,sha256=eTIOXt8uxH7diIFKzu-jhbuu155i_EWjDyFbEPgGAY8,941
6
+ cadwyn/main.py,sha256=ofTKT8LvOkFh5M19UgPNWLxf-3_yVOGNTVPnoADc3Q4,4894
7
+ cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ cadwyn/routing.py,sha256=wk60HazI0CPEjNQ1cd0D0le1nzb1tkT4eKkuIP3ImeA,33083
9
+ cadwyn/structure/__init__.py,sha256=2c6ivh7A-MQhhvovpsgiTsuk_ovYVVMWsqMqNC5QtuE,695
10
+ cadwyn/structure/common.py,sha256=Nc0IP4L6z_HNVFrLaeepG2NY0en2ycZndXllpgoloNg,291
11
+ cadwyn/structure/data.py,sha256=kzm3uf7VktH8nwShUk2hdfuuz0uYt9jzkRKMbWy0NtU,6564
12
+ cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
13
+ cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
14
+ cadwyn/structure/schemas.py,sha256=wpGFXzoq6qrOulEk7I_69UslapG5iD5crFLQp-kXEFQ,5241
15
+ cadwyn/structure/versions.py,sha256=8HNKFVoYXgHgWmvuFVBNkk5gghxWbWlLNC40e00Ba2k,24174
16
+ cadwyn-2.3.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
17
+ cadwyn-2.3.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
18
+ cadwyn-2.3.0.dist-info/WHEEL,sha256=vxFmldFsRN_Hx10GDvsdv1wroKq8r5Lzvjp6GZ4OO8c,88
19
+ cadwyn-2.3.0.dist-info/METADATA,sha256=uBZfJf64IvWhP37sJsIIAl7NTQbw7RvawsHPQFMTo9o,3853
20
+ cadwyn-2.3.0.dist-info/RECORD,,
cadwyn/header.py DELETED
@@ -1,28 +0,0 @@
1
- import datetime
2
- import types
3
- from collections.abc import Mapping
4
- from contextvars import ContextVar
5
- from typing import Any
6
-
7
- from fastapi import Depends, Header
8
-
9
-
10
- def get_cadwyn_dependency(
11
- *,
12
- version_header_name: str,
13
- default_version: datetime.date | None = None,
14
- extra_kwargs_to_header_constructor: Mapping[str, Any] = types.MappingProxyType({}),
15
- api_version_var: ContextVar[datetime.date | None] | ContextVar[datetime.date],
16
- ) -> Any:
17
- if default_version is None:
18
- extra_kwargs: Mapping[str, Any] = extra_kwargs_to_header_constructor
19
- else:
20
- extra_kwargs = extra_kwargs_to_header_constructor | {"default": default_version}
21
-
22
- async def dependency(
23
- api_version: datetime.date = Header(alias=version_header_name, **extra_kwargs), # noqa: B008
24
- ):
25
- api_version_var.set(api_version)
26
- return api_version
27
-
28
- return Depends(dependency)
@@ -1,21 +0,0 @@
1
- cadwyn/__init__.py,sha256=IGJOStZuQ1cw7f8W40nymA7eYGDVKrWDXz5trMxsy3U,524
2
- cadwyn/__main__.py,sha256=aKAwxVnhqi3ATd1UsifoLA1t3udTzz56t0BRTlktX1A,2845
3
- cadwyn/_utils.py,sha256=Uh6No0FNY1AR2Z2E19fMPIU9_J4lbuG8XOQU2AlDIZw,3600
4
- cadwyn/codegen.py,sha256=wIQgzUrMiYsyoMu9iDRbzUy3L2RHre5FSbbDmjwrpnw,38212
5
- cadwyn/exceptions.py,sha256=Utb6anOzrh97nOUgqCMmZHkQg8SFafLKSKO0EUPQ0yU,624
6
- cadwyn/header.py,sha256=2xw5wtxMQaGe2P7heSAvWu5GDHUEvWYpydZaQcKSc3s,901
7
- cadwyn/main.py,sha256=pUFGHmiHk8dpS2kxUiyll0T3XTti3MPVySdTecUd38s,5282
8
- cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- cadwyn/routing.py,sha256=66wA0CwAnqSrZvkxDm-X443AqnCrmpHtVUGGIrC2WUI,32598
10
- cadwyn/structure/__init__.py,sha256=2c6ivh7A-MQhhvovpsgiTsuk_ovYVVMWsqMqNC5QtuE,695
11
- cadwyn/structure/common.py,sha256=Nc0IP4L6z_HNVFrLaeepG2NY0en2ycZndXllpgoloNg,291
12
- cadwyn/structure/data.py,sha256=kzm3uf7VktH8nwShUk2hdfuuz0uYt9jzkRKMbWy0NtU,6564
13
- cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
14
- cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
15
- cadwyn/structure/schemas.py,sha256=wpGFXzoq6qrOulEk7I_69UslapG5iD5crFLQp-kXEFQ,5241
16
- cadwyn/structure/versions.py,sha256=uuZE44xANimqJqjwf1rW75iJDQ1GYDG-uR90Nt9MLNc,22287
17
- cadwyn-2.2.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
18
- cadwyn-2.2.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
19
- cadwyn-2.2.0.dist-info/WHEEL,sha256=vxFmldFsRN_Hx10GDvsdv1wroKq8r5Lzvjp6GZ4OO8c,88
20
- cadwyn-2.2.0.dist-info/METADATA,sha256=uaUm0-TMI90t1Xtd_9KTVq9sP3AzM83xl407EFzXNf4,3804
21
- cadwyn-2.2.0.dist-info/RECORD,,
File without changes