starmallow 0.7.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/__init__.py +1 -1
- starmallow/applications.py +196 -232
- starmallow/background.py +1 -1
- starmallow/concurrency.py +8 -7
- starmallow/dataclasses.py +11 -10
- starmallow/datastructures.py +1 -1
- starmallow/decorators.py +31 -30
- starmallow/delimited_field.py +37 -15
- starmallow/docs.py +5 -5
- starmallow/endpoint.py +105 -79
- starmallow/endpoints.py +3 -2
- starmallow/exceptions.py +3 -3
- starmallow/ext/marshmallow/openapi.py +13 -16
- starmallow/fields.py +3 -3
- starmallow/generics.py +34 -0
- starmallow/middleware/asyncexitstack.py +1 -2
- starmallow/params.py +20 -21
- starmallow/py.typed +0 -0
- starmallow/request_resolver.py +62 -58
- starmallow/responses.py +5 -4
- starmallow/routing.py +231 -239
- starmallow/schema_generator.py +98 -52
- starmallow/security/api_key.py +11 -11
- starmallow/security/base.py +12 -4
- starmallow/security/http.py +31 -26
- starmallow/security/oauth2.py +48 -48
- starmallow/security/open_id_connect_url.py +7 -7
- starmallow/security/utils.py +2 -5
- starmallow/serializers.py +59 -63
- starmallow/types.py +12 -8
- starmallow/utils.py +114 -70
- starmallow/websockets.py +3 -6
- {starmallow-0.7.0.dist-info → starmallow-0.9.0.dist-info}/METADATA +17 -16
- starmallow-0.9.0.dist-info/RECORD +43 -0
- {starmallow-0.7.0.dist-info → starmallow-0.9.0.dist-info}/WHEEL +1 -1
- starmallow/union_field.py +0 -86
- starmallow-0.7.0.dist-info/RECORD +0 -42
- {starmallow-0.7.0.dist-info → starmallow-0.9.0.dist-info}/licenses/LICENSE.md +0 -0
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
|
2
|
-
from
|
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
|
7
|
-
from starlette.concurrency import run_in_threadpool as run_in_threadpool
|
8
|
-
from starlette.concurrency import run_until_first_complete as run_until_first_complete
|
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:
|
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,
|
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:
|
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:
|
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:
|
84
|
+
metadata: dict[str, Any] | None = None,
|
84
85
|
# Marshmallow Schema kwargs
|
85
86
|
**schema_kwargs,
|
86
87
|
):
|
starmallow/datastructures.py
CHANGED
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
|
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:
|
24
|
+
status_code: int | None = None
|
24
25
|
middleware: Sequence[Middleware] | None = None
|
25
|
-
deprecated:
|
26
|
-
request_class:
|
27
|
-
response_model:
|
28
|
-
response_class:
|
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:
|
31
|
-
description:
|
31
|
+
summary: str | None = None
|
32
|
+
description: str | None = None
|
32
33
|
response_description: str = "Successful Response"
|
33
|
-
responses:
|
34
|
-
callbacks:
|
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:
|
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:
|
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:
|
45
|
-
route_class:
|
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:
|
53
|
+
status_code: int | None = None,
|
53
54
|
middleware: Sequence[Middleware] | None = None,
|
54
|
-
deprecated:
|
55
|
-
request_class:
|
56
|
-
response_model:
|
57
|
-
response_class:
|
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:
|
60
|
-
description:
|
60
|
+
summary: str | None = None,
|
61
|
+
description: str | None = None,
|
61
62
|
response_description: str = "Successful Response",
|
62
|
-
responses:
|
63
|
-
callbacks:
|
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:
|
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:
|
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:
|
72
|
-
route_class:
|
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,
|
starmallow/delimited_field.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
-
|
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)
|
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,
|
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:
|
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:
|
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__(
|
125
|
+
super().__init__(cls_or_instances, **kwargs)
|
starmallow/docs.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import json
|
2
|
-
from typing import Any
|
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:
|
24
|
-
init_oauth:
|
25
|
-
swagger_ui_parameters:
|
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
|
-
"""
|
199
|
+
"""
|
200
200
|
return HTMLResponse(content=html)
|