starmallow 0.8.0__py3-none-any.whl → 0.9.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.
starmallow/background.py CHANGED
@@ -23,7 +23,7 @@ class BackgroundTask(StarletteBackgroundTask):
23
23
 
24
24
  class BackgroundTasks(StarletteBackgroundTasks):
25
25
  def add_task(
26
- self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs
26
+ self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs,
27
27
  ) -> None:
28
28
  task = BackgroundTask(func, *args, **kwargs)
29
29
  self.tasks.append(task)
starmallow/concurrency.py CHANGED
@@ -1,18 +1,19 @@
1
- from contextlib import asynccontextmanager
2
- from typing import AsyncGenerator, ContextManager, TypeVar
1
+ from collections.abc import AsyncGenerator
2
+ from contextlib import AbstractContextManager, asynccontextmanager
3
+ from typing import TypeVar
3
4
 
4
- import anyio
5
+ import anyio.to_thread
5
6
  from anyio import CapacityLimiter
6
- from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa
7
- from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa
8
- from starlette.concurrency import run_until_first_complete as run_until_first_complete # noqa
7
+ from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool
8
+ from starlette.concurrency import run_in_threadpool as run_in_threadpool
9
+ from starlette.concurrency import run_until_first_complete as run_until_first_complete
9
10
 
10
11
  _T = TypeVar("_T")
11
12
 
12
13
 
13
14
  @asynccontextmanager
14
15
  async def contextmanager_in_threadpool(
15
- cm: ContextManager[_T],
16
+ cm: AbstractContextManager[_T],
16
17
  ) -> AsyncGenerator[_T, None]:
17
18
  # blocking __exit__ from running waiting on a free thread
18
19
  # can create race conditions/deadlocks if the context manager itself
starmallow/dataclasses.py CHANGED
@@ -1,5 +1,6 @@
1
+ from collections.abc import Callable
1
2
  from dataclasses import MISSING, field
2
- from typing import Any, Callable, Dict
3
+ from typing import Any, Literal
3
4
 
4
5
 
5
6
  ##############################################################
@@ -15,12 +16,12 @@ from typing import Any, Callable, Dict
15
16
  ##############################################################
16
17
  def required_field(
17
18
  default: Any = None,
18
- default_factory: Callable = MISSING,
19
+ default_factory: Callable | Literal[MISSING] = MISSING,
19
20
  dump_only: bool = False,
20
21
  load_only: bool = False,
21
- description: str = None,
22
+ description: str | None = None,
22
23
  # Marshmallow Schema metadata
23
- metadata: Dict[str, Any] = None,
24
+ metadata: dict[str, Any] | None = None,
24
25
  # Marshmallow Schema kwargs
25
26
  **schema_kwargs,
26
27
  ):
@@ -46,12 +47,12 @@ def required_field(
46
47
 
47
48
  def optional_field(
48
49
  default: Any = None,
49
- default_factory: Callable = MISSING,
50
+ default_factory: Callable | Literal[MISSING] = MISSING,
50
51
  dump_only: bool = False,
51
52
  load_only: bool = False,
52
- description: str = None,
53
+ description: str | None = None,
53
54
  # Marshmallow Schema metadata
54
- metadata: Dict[str, Any] = None,
55
+ metadata: dict[str, Any] | None = None,
55
56
  # Marshmallow Schema kwargs
56
57
  **schema_kwargs,
57
58
  ):
@@ -77,10 +78,10 @@ def optional_field(
77
78
 
78
79
  def dump_only_field(
79
80
  default: Any = None,
80
- default_factory: Callable = MISSING,
81
- description: str = None,
81
+ default_factory: Callable | Literal[MISSING] = MISSING,
82
+ description: str | None = None,
82
83
  # Marshmallow Schema metadata
83
- metadata: Dict[str, Any] = None,
84
+ metadata: dict[str, Any] | None = None,
84
85
  # Marshmallow Schema kwargs
85
86
  **schema_kwargs,
86
87
  ):
@@ -22,7 +22,7 @@ class DefaultPlaceholder:
22
22
  DefaultType = TypeVar("DefaultType")
23
23
 
24
24
 
25
- def Default(value: DefaultType) -> DefaultType:
25
+ def Default(value: DefaultType) -> DefaultType: # noqa: N802
26
26
  """
27
27
  You shouldn't use this function directly.
28
28
 
starmallow/decorators.py CHANGED
@@ -1,6 +1,7 @@
1
+ from collections.abc import Callable, Sequence
1
2
  from dataclasses import dataclass, field
2
3
  from enum import Enum
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Type, Union
4
+ from typing import TYPE_CHECKING, Any
4
5
 
5
6
  from starlette.middleware import Middleware
6
7
  from starlette.requests import Request
@@ -18,65 +19,65 @@ if TYPE_CHECKING: # pragma: nocover
18
19
 
19
20
  @dataclass
20
21
  class EndpointOptions:
21
- name: str = None
22
+ name: str | None = None
22
23
  include_in_schema: bool = True
23
- status_code: Optional[int] = None
24
+ status_code: int | None = None
24
25
  middleware: Sequence[Middleware] | None = None
25
- deprecated: Optional[bool] = None
26
- request_class: Type[Request] = Request
27
- response_model: Optional[Type[Any]] = None
28
- response_class: Type[Response] = JSONResponse
26
+ deprecated: bool | None = None
27
+ request_class: type[Request] = Request
28
+ response_model: type[Any] | None = None
29
+ response_class: type[Response] = JSONResponse
29
30
  # OpenAPI summary
30
- summary: Optional[str] = None
31
- description: Optional[str] = None
31
+ summary: str | None = None
32
+ description: str | None = None
32
33
  response_description: str = "Successful Response"
33
- responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None
34
- callbacks: Optional[List[BaseRoute]] = None
34
+ responses: dict[int | str, dict[str, Any]] | None = None
35
+ callbacks: list[BaseRoute] | None = None
35
36
  # Sets the OpenAPI operationId to be used in your path operation
36
- operation_id: Optional[str] = None
37
+ operation_id: str | None = None
37
38
  # If operation_id is None, this function will be used to create one.
38
39
  generate_unique_id_function: Callable[["APIRoute"], str] = field(
39
40
  default_factory=lambda: Default(generate_unique_id),
40
41
  )
41
42
  # OpenAPI tags
42
- tags: Optional[List[Union[str, Enum]]] = None
43
+ tags: list[str | Enum] | None = None
43
44
  # Will be deeply merged with the automatically generated OpenAPI schema for the path operation.
44
- openapi_extra: Optional[Dict[str, Any]] = None
45
- route_class: Optional[Type["APIRoute"]] = None
45
+ openapi_extra: dict[str, Any] | None = None
46
+ route_class: type["APIRoute"] | None = None
46
47
 
47
48
 
48
49
  def route(
49
50
  *,
50
- name: str = None,
51
+ name: str | None = None,
51
52
  include_in_schema: bool = True,
52
- status_code: Optional[int] = None,
53
+ status_code: int | None = None,
53
54
  middleware: Sequence[Middleware] | None = None,
54
- deprecated: Optional[bool] = None,
55
- request_class: Type[Request] = Default(Request),
56
- response_model: Optional[Type[Any]] = None,
57
- response_class: Type[Response] = JSONResponse,
55
+ deprecated: bool | None = None,
56
+ request_class: type[Request] = Default(Request),
57
+ response_model: type[Any] | None = None,
58
+ response_class: type[Response] = JSONResponse,
58
59
  # OpenAPI summary
59
- summary: Optional[str] = None,
60
- description: Optional[str] = None,
60
+ summary: str | None = None,
61
+ description: str | None = None,
61
62
  response_description: str = "Successful Response",
62
- responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
63
- callbacks: Optional[List[BaseRoute]] = None,
63
+ responses: dict[int | str, dict[str, Any]] | None = None,
64
+ callbacks: list[BaseRoute] | None = None,
64
65
  # Sets the OpenAPI operationId to be used in your path operation
65
- operation_id: Optional[str] = None,
66
+ operation_id: str | None = None,
66
67
  # If operation_id is None, this function will be used to create one.
67
68
  generate_unique_id_function: Callable[["APIRoute"], str] = Default(generate_unique_id),
68
69
  # OpenAPI tags
69
- tags: Optional[List[Union[str, Enum]]] = None,
70
+ tags: list[str | Enum] | None = None,
70
71
  # Will be deeply merged with the automatically generated OpenAPI schema for the path operation.
71
- openapi_extra: Optional[Dict[str, Any]] = None,
72
- route_class: Optional[Type["APIRoute"]] = None,
72
+ openapi_extra: dict[str, Any] | None = None,
73
+ route_class: type["APIRoute"] | None = None,
73
74
  ) -> Callable[[DecoratedCallable], DecoratedCallable]:
74
75
  '''
75
76
  Intended to be used with APIHTTPEndpoint to override options on a per method basis
76
77
  '''
77
78
  def decorator(func: DecoratedCallable) -> DecoratedCallable:
78
79
  # Attach options to the function so we can call upon them when we process the class
79
- func.endpoint_options = EndpointOptions(
80
+ func.endpoint_options = EndpointOptions( # type: ignore
80
81
  name=name,
81
82
  include_in_schema=include_in_schema,
82
83
  status_code=status_code,
@@ -1,4 +1,4 @@
1
- ## Copied from webargs, but allows for None values.
1
+ # Copied from webargs, but allows for None values.
2
2
 
3
3
  """Field classes.
4
4
 
@@ -15,10 +15,30 @@ tells webargs where to parse the request argument from.
15
15
  "content_type": fields.Str(data_key="Content-Type", location="headers"),
16
16
  }
17
17
  """
18
- from typing import Union
18
+ import sys
19
+ from typing import Any, ClassVar, Generic, Protocol, TypeVar
19
20
 
20
21
  import marshmallow as ma
21
22
 
23
+ if sys.version_info >= (3, 11):
24
+ from typing import TypeVarTuple
25
+ else:
26
+ # Python 3.10 and below
27
+ from typing_extensions import TypeVarTuple
28
+
29
+ from .generics import get_orig_class
30
+
31
+ T = TypeVar('T', bound=ma.fields.Field | type[ma.fields.Field])
32
+ Ts = TypeVarTuple('Ts') # Bound is not supported, bound=ma.fields.Field | type[ma.fields.Field]
33
+
34
+
35
+ class _SupportListOrTupleField(Protocol):
36
+ delimiter: str
37
+
38
+ def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs) -> list | tuple: ...
39
+ def _deserialize(self, value, attr: str | None, data, **kwargs): ...
40
+ def make_error(self, key: str, **kwargs) -> ma.ValidationError: ...
41
+
22
42
 
23
43
  class DelimitedFieldMixin:
24
44
  """
@@ -38,24 +58,26 @@ class DelimitedFieldMixin:
38
58
  # delimited fields set is_multiple=False for webargs.core.is_multiple
39
59
  is_multiple: bool = False
40
60
 
41
- def _serialize(self, value, attr, obj, **kwargs):
61
+ def _serialize(self: _SupportListOrTupleField, value: Any, attr: str | None, obj: Any, **kwargs):
42
62
  # serializing will start with parent-class serialization, so that we correctly
43
63
  # output lists of non-primitive types, e.g. DelimitedList(DateTime)
44
64
  if value is not None:
45
65
  return self.delimiter.join(
46
- format(each) for each in super()._serialize(value, attr, obj, **kwargs)
66
+ format(each) if each is not None else ''
67
+ for each in super()._serialize(value, attr, obj, **kwargs)
47
68
  )
48
69
 
49
- def _deserialize(self, value, attr, data, **kwargs):
70
+ def _deserialize(self: _SupportListOrTupleField, value, attr: str | None, data, **kwargs):
50
71
  if value is not None:
51
72
  # attempting to deserialize from a non-string source is an error
52
- if not isinstance(value, (str, bytes)):
73
+ if not isinstance(value, str):
53
74
  raise self.make_error("invalid")
75
+
54
76
  values = value.split(self.delimiter) if value else []
55
77
  return super()._deserialize(values, attr, data, **kwargs)
56
78
 
57
79
 
58
- class DelimitedList(DelimitedFieldMixin, ma.fields.List):
80
+ class DelimitedList(DelimitedFieldMixin, ma.fields.List, Generic[T]): # type: ignore
59
81
  """A field which is similar to a List, but takes its input as a delimited
60
82
  string (e.g. "foo,bar,baz").
61
83
 
@@ -66,20 +88,20 @@ class DelimitedList(DelimitedFieldMixin, ma.fields.List):
66
88
  :param str delimiter: Delimiter between values.
67
89
  """
68
90
 
69
- default_error_messages = {"invalid": "Not a valid delimited list."}
91
+ default_error_messages: ClassVar[dict[str, str]] = {"invalid": "Not a valid delimited list."}
70
92
 
71
93
  def __init__(
72
94
  self,
73
- cls_or_instance: Union[ma.fields.Field, type],
74
95
  *,
75
- delimiter: Union[str, None] = None,
96
+ delimiter: str | None = None,
76
97
  **kwargs,
77
98
  ):
99
+ cls_or_instance = get_orig_class(self).__args__[0]
78
100
  self.delimiter = delimiter or self.delimiter
79
101
  super().__init__(cls_or_instance, **kwargs)
80
102
 
81
103
 
82
- class DelimitedTuple(DelimitedFieldMixin, ma.fields.Tuple):
104
+ class DelimitedTuple(DelimitedFieldMixin, ma.fields.Tuple, Generic[Ts]): # type: ignore
83
105
  """A field which is similar to a Tuple, but takes its input as a delimited
84
106
  string (e.g. "foo,bar,baz").
85
107
 
@@ -90,14 +112,14 @@ class DelimitedTuple(DelimitedFieldMixin, ma.fields.Tuple):
90
112
  :param str delimiter: Delimiter between values.
91
113
  """
92
114
 
93
- default_error_messages = {"invalid": "Not a valid delimited tuple."}
115
+ default_error_messages: ClassVar[dict[str, str]] = {"invalid": "Not a valid delimited tuple."}
94
116
 
95
117
  def __init__(
96
118
  self,
97
- tuple_fields,
98
119
  *,
99
- delimiter: Union[str, None] = None,
120
+ delimiter: str | None = None,
100
121
  **kwargs,
101
122
  ):
123
+ cls_or_instances = get_orig_class(self).__args__
102
124
  self.delimiter = delimiter or self.delimiter
103
- super().__init__(tuple_fields, **kwargs)
125
+ super().__init__(cls_or_instances, **kwargs)
starmallow/docs.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Dict, Optional
2
+ from typing import Any
3
3
 
4
4
  from starlette.responses import HTMLResponse
5
5
 
@@ -20,9 +20,9 @@ def get_swagger_ui_html(
20
20
  title: str,
21
21
  swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
22
22
  swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
23
- oauth2_redirect_url: Optional[str] = None,
24
- init_oauth: Optional[Dict[str, Any]] = None,
25
- swagger_ui_parameters: Optional[Dict[str, Any]] = None,
23
+ oauth2_redirect_url: str | None = None,
24
+ init_oauth: dict[str, Any] | None = None,
25
+ swagger_ui_parameters: dict[str, Any] | None = None,
26
26
  ) -> HTMLResponse:
27
27
  current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
28
28
  if swagger_ui_parameters:
@@ -196,5 +196,5 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
196
196
  </script>
197
197
  </body>
198
198
  </html>
199
- """ # noqa: E501
199
+ """
200
200
  return HTMLResponse(content=html)