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/security/oauth2.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Annotated, Any, ClassVar
|
2
2
|
|
3
3
|
import marshmallow as ma
|
4
|
-
from
|
4
|
+
from marshmallow_dataclass2 import dataclass as ma_dataclass
|
5
5
|
from starlette.requests import Request
|
6
6
|
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
7
7
|
|
@@ -15,14 +15,14 @@ from starmallow.security.utils import get_authorization_scheme_param
|
|
15
15
|
# region - Models
|
16
16
|
@ma_dataclass(frozen=True)
|
17
17
|
class OAuthFlow:
|
18
|
-
refreshUrl:
|
19
|
-
scopes:
|
18
|
+
refreshUrl: str | None = optional_field()
|
19
|
+
scopes: dict[str, str] = optional_field(default_factory=dict)
|
20
20
|
|
21
21
|
class Meta:
|
22
22
|
unknown = ma.INCLUDE
|
23
23
|
|
24
24
|
@ma.post_dump()
|
25
|
-
def post_dump(self, data:
|
25
|
+
def post_dump(self, data: dict[str, Any], **kwargs):
|
26
26
|
# Remove None values
|
27
27
|
return {
|
28
28
|
key: value
|
@@ -54,16 +54,16 @@ class OAuthFlowAuthorizationCode(OAuthFlow):
|
|
54
54
|
|
55
55
|
@ma_dataclass(frozen=True)
|
56
56
|
class OAuthFlowsModel:
|
57
|
-
implicit:
|
58
|
-
password:
|
59
|
-
clientCredentials:
|
60
|
-
authorizationCode:
|
57
|
+
implicit: OAuthFlowImplicit | None = optional_field()
|
58
|
+
password: OAuthFlowPassword | None = optional_field()
|
59
|
+
clientCredentials: OAuthFlowClientCredentials | None = optional_field()
|
60
|
+
authorizationCode: OAuthFlowAuthorizationCode | None = optional_field()
|
61
61
|
|
62
62
|
class Meta:
|
63
63
|
unknown = ma.INCLUDE
|
64
64
|
|
65
65
|
@ma.post_dump()
|
66
|
-
def post_dump(self, data:
|
66
|
+
def post_dump(self, data: dict[str, Any], **kwargs):
|
67
67
|
# Remove None values
|
68
68
|
return {
|
69
69
|
key: value
|
@@ -74,7 +74,7 @@ class OAuthFlowsModel:
|
|
74
74
|
|
75
75
|
@ma_dataclass(frozen=True)
|
76
76
|
class OAuth2Model(SecurityBase):
|
77
|
-
type: SecurityTypes = SecurityTypes.oauth2
|
77
|
+
type: ClassVar[SecurityTypes] = SecurityTypes.oauth2
|
78
78
|
flows: OAuthFlowsModel = required_field()
|
79
79
|
# endregion
|
80
80
|
|
@@ -114,12 +114,12 @@ class OAuth2PasswordRequestForm:
|
|
114
114
|
|
115
115
|
def __init__(
|
116
116
|
self,
|
117
|
-
grant_type: str
|
118
|
-
username: str
|
119
|
-
password: str
|
120
|
-
scope: str
|
121
|
-
client_id:
|
122
|
-
client_secret:
|
117
|
+
grant_type: Annotated[str, Form(default=None, regex="password")],
|
118
|
+
username: Annotated[str, Form()],
|
119
|
+
password: Annotated[str, Form()],
|
120
|
+
scope: Annotated[str, Form(default="")],
|
121
|
+
client_id: Annotated[str | None, Form(default=None)],
|
122
|
+
client_secret: Annotated[str | None, Form(default=None)],
|
123
123
|
):
|
124
124
|
self.grant_type = grant_type
|
125
125
|
self.username = username
|
@@ -164,12 +164,12 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
|
164
164
|
|
165
165
|
def __init__(
|
166
166
|
self,
|
167
|
-
grant_type: str
|
168
|
-
username: str
|
169
|
-
password: str
|
170
|
-
scope: str
|
171
|
-
client_id:
|
172
|
-
client_secret:
|
167
|
+
grant_type: Annotated[str, Form(regex="password")],
|
168
|
+
username: Annotated[str, Form()],
|
169
|
+
password: Annotated[str, Form()],
|
170
|
+
scope: Annotated[str, Form(default="")],
|
171
|
+
client_id: Annotated[str | None, Form(default=None)],
|
172
|
+
client_secret:Annotated[str | None, Form(default=None)],
|
173
173
|
):
|
174
174
|
super().__init__(
|
175
175
|
grant_type=grant_type,
|
@@ -185,16 +185,16 @@ class OAuth2(SecurityBaseResolver):
|
|
185
185
|
def __init__(
|
186
186
|
self,
|
187
187
|
*,
|
188
|
-
flows:
|
189
|
-
scheme_name:
|
190
|
-
description:
|
188
|
+
flows: OAuthFlowsModel | None = None,
|
189
|
+
scheme_name: str | None = None,
|
190
|
+
description: str | None = None,
|
191
191
|
auto_error: bool = True,
|
192
192
|
):
|
193
|
-
self.model = OAuth2Model(flows=flows, description=description)
|
193
|
+
self.model = OAuth2Model(flows=flows or OAuthFlowsModel(), description=description)
|
194
194
|
self.scheme_name = scheme_name or self.__class__.__name__
|
195
195
|
self.auto_error = auto_error
|
196
196
|
|
197
|
-
async def __call__(self, request: Request) ->
|
197
|
+
async def __call__(self, request: Request) -> str | None:
|
198
198
|
authorization = request.headers.get("Authorization")
|
199
199
|
if not authorization:
|
200
200
|
if self.auto_error:
|
@@ -209,15 +209,15 @@ class OAuth2(SecurityBaseResolver):
|
|
209
209
|
class OAuth2PasswordBearer(OAuth2):
|
210
210
|
def __init__(
|
211
211
|
self,
|
212
|
-
tokenUrl: str,
|
213
|
-
scheme_name:
|
214
|
-
scopes:
|
215
|
-
description:
|
212
|
+
tokenUrl: str, # noqa: N803
|
213
|
+
scheme_name: str | None = None,
|
214
|
+
scopes: dict[str, str] | None = None,
|
215
|
+
description: str | None = None,
|
216
216
|
auto_error: bool = True,
|
217
217
|
):
|
218
218
|
if not scopes:
|
219
219
|
scopes = {}
|
220
|
-
flows = OAuthFlowsModel(password=
|
220
|
+
flows = OAuthFlowsModel(password=OAuthFlowPassword(tokenUrl=tokenUrl, scopes=scopes))
|
221
221
|
super().__init__(
|
222
222
|
flows=flows,
|
223
223
|
scheme_name=scheme_name,
|
@@ -225,7 +225,7 @@ class OAuth2PasswordBearer(OAuth2):
|
|
225
225
|
auto_error=auto_error,
|
226
226
|
)
|
227
227
|
|
228
|
-
async def __call__(self, request: Request) ->
|
228
|
+
async def __call__(self, request: Request) -> str | None:
|
229
229
|
authorization = request.headers.get("Authorization")
|
230
230
|
scheme, param = get_authorization_scheme_param(authorization)
|
231
231
|
if not authorization or scheme.lower() != "bearer":
|
@@ -243,23 +243,23 @@ class OAuth2PasswordBearer(OAuth2):
|
|
243
243
|
class OAuth2AuthorizationCodeBearer(OAuth2):
|
244
244
|
def __init__(
|
245
245
|
self,
|
246
|
-
authorizationUrl: str,
|
247
|
-
tokenUrl: str,
|
248
|
-
|
249
|
-
scheme_name:
|
250
|
-
scopes:
|
251
|
-
description:
|
246
|
+
authorizationUrl: str, # noqa: N803
|
247
|
+
tokenUrl: str, # noqa: N803
|
248
|
+
refresh_url: str | None = None,
|
249
|
+
scheme_name: str | None = None,
|
250
|
+
scopes: dict[str, str] | None = None,
|
251
|
+
description: str | None = None,
|
252
252
|
auto_error: bool = True,
|
253
253
|
):
|
254
254
|
if not scopes:
|
255
255
|
scopes = {}
|
256
256
|
flows = OAuthFlowsModel(
|
257
|
-
authorizationCode=
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
257
|
+
authorizationCode=OAuthFlowAuthorizationCode(
|
258
|
+
authorizationUrl=authorizationUrl,
|
259
|
+
tokenUrl=tokenUrl,
|
260
|
+
refreshUrl=refresh_url,
|
261
|
+
scopes=scopes,
|
262
|
+
),
|
263
263
|
)
|
264
264
|
super().__init__(
|
265
265
|
flows=flows,
|
@@ -268,7 +268,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
|
268
268
|
auto_error=auto_error,
|
269
269
|
)
|
270
270
|
|
271
|
-
async def __call__(self, request: Request) ->
|
271
|
+
async def __call__(self, request: Request) -> str | None:
|
272
272
|
authorization = request.headers.get("Authorization")
|
273
273
|
scheme, param = get_authorization_scheme_param(authorization)
|
274
274
|
if not authorization or scheme.lower() != "bearer":
|
@@ -284,6 +284,6 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
|
284
284
|
|
285
285
|
|
286
286
|
class SecurityScopes:
|
287
|
-
def __init__(self, scopes:
|
287
|
+
def __init__(self, scopes: list[str] | None = None):
|
288
288
|
self.scopes = scopes or []
|
289
289
|
self.scope_str = " ".join(self.scopes)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import ClassVar
|
2
2
|
|
3
|
-
from
|
3
|
+
from marshmallow_dataclass2 import dataclass as ma_dataclass
|
4
4
|
from starlette.exceptions import HTTPException
|
5
5
|
from starlette.requests import Request
|
6
6
|
from starlette.status import HTTP_403_FORBIDDEN
|
@@ -11,7 +11,7 @@ from starmallow.security.base import SecurityBase, SecurityBaseResolver, Securit
|
|
11
11
|
|
12
12
|
@ma_dataclass(frozen=True)
|
13
13
|
class OpenIdConnectModel(SecurityBase):
|
14
|
-
type: SecurityTypes = SecurityTypes.openIdConnect
|
14
|
+
type: ClassVar[SecurityTypes] = SecurityTypes.openIdConnect
|
15
15
|
openIdConnectUrl: str = required_field()
|
16
16
|
|
17
17
|
|
@@ -19,9 +19,9 @@ class OpenIdConnect(SecurityBaseResolver):
|
|
19
19
|
def __init__(
|
20
20
|
self,
|
21
21
|
*,
|
22
|
-
openIdConnectUrl: str,
|
23
|
-
scheme_name:
|
24
|
-
description:
|
22
|
+
openIdConnectUrl: str, # noqa: N803
|
23
|
+
scheme_name: str | None = None,
|
24
|
+
description: str | None = None,
|
25
25
|
auto_error: bool = True,
|
26
26
|
):
|
27
27
|
self.model = OpenIdConnectModel(
|
@@ -30,7 +30,7 @@ class OpenIdConnect(SecurityBaseResolver):
|
|
30
30
|
self.scheme_name = scheme_name or self.__class__.__name__
|
31
31
|
self.auto_error = auto_error
|
32
32
|
|
33
|
-
async def __call__(self, request: Request) ->
|
33
|
+
async def __call__(self, request: Request) -> str | None:
|
34
34
|
authorization = request.headers.get("Authorization")
|
35
35
|
if not authorization:
|
36
36
|
if self.auto_error:
|
starmallow/security/utils.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
from typing import Optional, Tuple
|
2
|
-
|
3
|
-
|
4
1
|
def get_authorization_scheme_param(
|
5
|
-
authorization_header_value:
|
6
|
-
) ->
|
2
|
+
authorization_header_value: str | None,
|
3
|
+
) -> tuple[str, str]:
|
7
4
|
if not authorization_header_value:
|
8
5
|
return "", ""
|
9
6
|
scheme, _, param = authorization_header_value.partition(" ")
|
starmallow/serializers.py
CHANGED
@@ -8,52 +8,53 @@ from uuid import UUID
|
|
8
8
|
|
9
9
|
logger = logging.getLogger(__name__)
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
11
|
+
|
12
|
+
def json_default(o):
|
13
|
+
if isinstance(o, bytes):
|
14
|
+
return o.hex()
|
15
|
+
elif isinstance(o, set):
|
16
|
+
return list(o)
|
17
|
+
elif isinstance(o, Decimal):
|
18
|
+
return str(o)
|
19
|
+
elif isinstance(o, datetime | date):
|
20
|
+
return o.isoformat()
|
21
|
+
elif isinstance(o, UUID):
|
22
|
+
return str(o)
|
23
|
+
elif is_dataclass(o) and not isinstance(o, type):
|
24
|
+
return asdict(o)
|
25
|
+
elif isinstance(o, Enum):
|
26
|
+
return o.name
|
26
27
|
|
27
28
|
|
28
29
|
class JSONEncoder(json.JSONEncoder):
|
29
30
|
'''
|
30
31
|
Simple JSONEncoder that handles additional types
|
31
32
|
'''
|
32
|
-
def default(self,
|
33
|
-
if isinstance(
|
34
|
-
return
|
35
|
-
elif isinstance(
|
36
|
-
return list(
|
37
|
-
elif isinstance(
|
38
|
-
return str(
|
39
|
-
elif isinstance(
|
40
|
-
return
|
41
|
-
elif isinstance(
|
42
|
-
return str(
|
43
|
-
elif is_dataclass(
|
44
|
-
return asdict(
|
45
|
-
elif isinstance(
|
46
|
-
return
|
33
|
+
def default(self, o):
|
34
|
+
if isinstance(o, bytes):
|
35
|
+
return o.hex()
|
36
|
+
elif isinstance(o, set):
|
37
|
+
return list(o)
|
38
|
+
elif isinstance(o, Decimal):
|
39
|
+
return str(o)
|
40
|
+
elif isinstance(o, datetime | date):
|
41
|
+
return o.isoformat()
|
42
|
+
elif isinstance(o, UUID):
|
43
|
+
return str(o)
|
44
|
+
elif is_dataclass(o) and not isinstance(o, type):
|
45
|
+
return asdict(o)
|
46
|
+
elif isinstance(o, Enum):
|
47
|
+
return o.name
|
47
48
|
# If a class and not any of the default types, automatically try to parse the object's attributes
|
48
|
-
elif not isinstance(
|
49
|
+
elif not isinstance(o, str | int | float | type(None) | list | dict):
|
49
50
|
try:
|
50
|
-
data = vars(
|
51
|
+
data = vars(o)
|
51
52
|
except Exception:
|
52
|
-
logger.exception(f'Failed to encode {
|
53
|
+
logger.exception(f'Failed to encode {o}')
|
53
54
|
return None
|
54
55
|
else:
|
55
56
|
return data
|
56
|
-
return json.JSONEncoder.default(self,
|
57
|
+
return json.JSONEncoder.default(self, o)
|
57
58
|
|
58
59
|
def encode(self, o):
|
59
60
|
"""Return a JSON string representation of a Python data structure.
|
@@ -78,12 +79,12 @@ class JSONEncoder(json.JSONEncoder):
|
|
78
79
|
# The remainder would be a little slower, but acceptably so.
|
79
80
|
try:
|
80
81
|
chunks = self.iterencode(o, _one_shot=True)
|
81
|
-
if not isinstance(chunks,
|
82
|
+
if not isinstance(chunks, list | tuple):
|
82
83
|
chunks = list(chunks)
|
83
84
|
except TypeError as e:
|
84
85
|
if e.args[0].startswith('keys must be '):
|
85
86
|
chunks = self.iterencode(o, _one_shot=True, use_python_iterencode=True)
|
86
|
-
if not isinstance(chunks,
|
87
|
+
if not isinstance(chunks, list | tuple):
|
87
88
|
chunks = list(chunks)
|
88
89
|
else:
|
89
90
|
raise
|
@@ -100,14 +101,12 @@ class JSONEncoder(json.JSONEncoder):
|
|
100
101
|
mysocket.write(chunk)
|
101
102
|
|
102
103
|
"""
|
103
|
-
if self.check_circular
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
else:
|
110
|
-
_encoder = json.encoder.encode_basestring
|
104
|
+
markers = {} if self.check_circular else None
|
105
|
+
_encoder = (
|
106
|
+
json.encoder.encode_basestring_ascii
|
107
|
+
if self.ensure_ascii
|
108
|
+
else json.encoder.encode_basestring
|
109
|
+
)
|
111
110
|
|
112
111
|
def floatstr(
|
113
112
|
o,
|
@@ -130,17 +129,17 @@ class JSONEncoder(json.JSONEncoder):
|
|
130
129
|
return _repr(o)
|
131
130
|
|
132
131
|
if not allow_nan:
|
133
|
-
raise ValueError(f"Out of range float values are not JSON compliant: {
|
132
|
+
raise ValueError(f"Out of range float values are not JSON compliant: {o!r}")
|
134
133
|
|
135
134
|
return text
|
136
135
|
|
137
136
|
if (
|
138
137
|
_one_shot
|
139
|
-
and json.encoder.c_make_encoder is not None
|
138
|
+
and json.encoder.c_make_encoder is not None # type: ignore
|
140
139
|
and self.indent is None
|
141
140
|
and not use_python_iterencode
|
142
141
|
):
|
143
|
-
_iterencode = json.encoder.c_make_encoder(
|
142
|
+
_iterencode = json.encoder.c_make_encoder( # type: ignore
|
144
143
|
markers, self.default, _encoder, self.indent,
|
145
144
|
self.key_separator, self.item_separator, self.sort_keys,
|
146
145
|
self.skipkeys, self.allow_nan,
|
@@ -166,15 +165,15 @@ def _make_iterencode(
|
|
166
165
|
_skipkeys,
|
167
166
|
_one_shot,
|
168
167
|
## HACK: hand-optimized bytecode; turn globals into locals
|
169
|
-
ValueError=ValueError,
|
170
|
-
dict=dict,
|
171
|
-
float=float,
|
172
|
-
id=id,
|
173
|
-
int=int,
|
174
|
-
isinstance=isinstance,
|
175
|
-
list=list,
|
176
|
-
str=str,
|
177
|
-
tuple=tuple,
|
168
|
+
ValueError=ValueError, # noqa: A002, N803
|
169
|
+
dict=dict, # noqa: A002
|
170
|
+
float=float, # noqa: A002
|
171
|
+
id=id, # noqa: A002
|
172
|
+
int=int, # noqa: A002
|
173
|
+
isinstance=isinstance, # noqa: A002
|
174
|
+
list=list, # noqa: A002
|
175
|
+
str=str, # noqa: A002
|
176
|
+
tuple=tuple, # noqa: A002
|
178
177
|
_intstr=int.__repr__,
|
179
178
|
):
|
180
179
|
|
@@ -235,7 +234,7 @@ def _make_iterencode(
|
|
235
234
|
yield '\n' + _indent * _current_indent_level
|
236
235
|
yield ']'
|
237
236
|
if markers is not None:
|
238
|
-
del markers[markerid]
|
237
|
+
del markers[markerid] # type: ignore
|
239
238
|
|
240
239
|
def _iterencode_dict(dct, _current_indent_level):
|
241
240
|
if not dct:
|
@@ -256,10 +255,7 @@ def _make_iterencode(
|
|
256
255
|
newline_indent = None
|
257
256
|
item_separator = _item_separator
|
258
257
|
first = True
|
259
|
-
if _sort_keys
|
260
|
-
items = sorted(dct.items())
|
261
|
-
else:
|
262
|
-
items = dct.items()
|
258
|
+
items = sorted(dct.items()) if _sort_keys else dct.items()
|
263
259
|
for key, value in items:
|
264
260
|
if isinstance(key, str):
|
265
261
|
pass
|
@@ -324,7 +320,7 @@ def _make_iterencode(
|
|
324
320
|
yield '\n' + _indent * _current_indent_level
|
325
321
|
yield '}'
|
326
322
|
if markers is not None:
|
327
|
-
del markers[markerid]
|
323
|
+
del markers[markerid] # type: ignore
|
328
324
|
|
329
325
|
def _iterencode(o, _current_indent_level):
|
330
326
|
if isinstance(o, str):
|
@@ -354,5 +350,5 @@ def _make_iterencode(
|
|
354
350
|
o = _default(o)
|
355
351
|
yield from _iterencode(o, _current_indent_level)
|
356
352
|
if markers is not None:
|
357
|
-
del markers[markerid]
|
353
|
+
del markers[markerid] # type: ignore
|
358
354
|
return _iterencode
|
starmallow/types.py
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
import uuid
|
2
|
-
from
|
2
|
+
from collections.abc import Awaitable, Callable
|
3
|
+
from typing import Annotated, Any, TypeVar
|
3
4
|
|
4
5
|
import marshmallow.fields as mf
|
5
|
-
from
|
6
|
+
from starlette.websockets import WebSocket
|
6
7
|
|
7
8
|
import starmallow.fields as sf
|
9
|
+
from starmallow.delimited_field import DelimitedList
|
8
10
|
from starmallow.endpoints import APIHTTPEndpoint
|
9
11
|
|
10
|
-
|
12
|
+
EndpointCallable = Callable[[], Awaitable[Any] | Any] | Callable[[...], Awaitable[Any] | Any]
|
13
|
+
WebSocketEndpointCallable = Callable[[WebSocket], Awaitable[None]]
|
14
|
+
DecoratedCallable = TypeVar("DecoratedCallable", bound=EndpointCallable | type[APIHTTPEndpoint])
|
11
15
|
|
12
|
-
UUID =
|
13
|
-
DelimitedListUUID =
|
14
|
-
DelimitedListStr =
|
15
|
-
DelimitedListInt =
|
16
|
+
UUID = Annotated[uuid.UUID, mf.UUID]
|
17
|
+
DelimitedListUUID = Annotated[list[uuid.UUID], DelimitedList[mf.UUID]]
|
18
|
+
DelimitedListStr = Annotated[list[str], DelimitedList[mf.String]]
|
19
|
+
DelimitedListInt = Annotated[list[int], DelimitedList[mf.Integer]]
|
16
20
|
|
17
|
-
HttpUrl =
|
21
|
+
HttpUrl = Annotated[str, sf.HttpUrl]
|
18
22
|
|
19
23
|
# Aliases
|
20
24
|
UUIDType = UUID
|