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.
@@ -1,7 +1,7 @@
1
- from typing import Any, Dict, List, Optional, Union
1
+ from typing import Annotated, Any, ClassVar
2
2
 
3
3
  import marshmallow as ma
4
- from marshmallow_dataclass import dataclass as ma_dataclass
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: Optional[str] = optional_field()
19
- scopes: Dict[str, str] = optional_field(default_factory=dict)
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: Dict[str, Any], **kwargs):
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: Optional[OAuthFlowImplicit] = optional_field()
58
- password: Optional[OAuthFlowPassword] = optional_field()
59
- clientCredentials: Optional[OAuthFlowClientCredentials] = optional_field()
60
- authorizationCode: Optional[OAuthFlowAuthorizationCode] = optional_field()
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: Dict[str, Any], **kwargs):
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 = Form(default=None, regex="password"),
118
- username: str = Form(),
119
- password: str = Form(),
120
- scope: str = Form(default=""),
121
- client_id: Optional[str] = Form(default=None),
122
- client_secret: Optional[str] = Form(default=None),
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 = Form(regex="password"),
168
- username: str = Form(),
169
- password: str = Form(),
170
- scope: str = Form(default=""),
171
- client_id: Optional[str] = Form(default=None),
172
- client_secret: Optional[str] = Form(default=None),
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: Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]] = OAuthFlowsModel(),
189
- scheme_name: Optional[str] = None,
190
- description: Optional[str] = None,
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) -> Optional[str]:
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: Optional[str] = None,
214
- scopes: Optional[Dict[str, str]] = None,
215
- description: Optional[str] = None,
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={"tokenUrl": tokenUrl, "scopes": scopes})
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) -> Optional[str]:
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
- refreshUrl: Optional[str] = None,
249
- scheme_name: Optional[str] = None,
250
- scopes: Optional[Dict[str, str]] = None,
251
- description: Optional[str] = None,
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
- "authorizationUrl": authorizationUrl,
259
- "tokenUrl": tokenUrl,
260
- "refreshUrl": refreshUrl,
261
- "scopes": scopes,
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) -> Optional[str]:
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: Optional[List[str]] = None):
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 Optional
1
+ from typing import ClassVar
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
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: Optional[str] = None,
24
- description: Optional[str] = None,
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) -> Optional[str]:
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:
@@ -1,9 +1,6 @@
1
- from typing import Optional, Tuple
2
-
3
-
4
1
  def get_authorization_scheme_param(
5
- authorization_header_value: Optional[str],
6
- ) -> Tuple[str, str]:
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
- def json_default(obj):
12
- if isinstance(obj, bytes):
13
- return obj.hex()
14
- elif isinstance(obj, set):
15
- return list(obj)
16
- elif isinstance(obj, Decimal):
17
- return str(obj)
18
- elif isinstance(obj, (datetime, date)):
19
- return obj.isoformat()
20
- elif isinstance(obj, UUID):
21
- return str(obj)
22
- elif is_dataclass(obj):
23
- return asdict(obj)
24
- elif isinstance(obj, Enum):
25
- return obj.name
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, obj):
33
- if isinstance(obj, bytes):
34
- return obj.hex()
35
- elif isinstance(obj, set):
36
- return list(obj)
37
- elif isinstance(obj, Decimal):
38
- return str(obj)
39
- elif isinstance(obj, (datetime, date)):
40
- return obj.isoformat()
41
- elif isinstance(obj, UUID):
42
- return str(obj)
43
- elif is_dataclass(obj):
44
- return asdict(obj)
45
- elif isinstance(obj, Enum):
46
- return obj.name
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(obj, (str, int, float, type(None), list, dict)):
49
+ elif not isinstance(o, str | int | float | type(None) | list | dict):
49
50
  try:
50
- data = vars(obj)
51
+ data = vars(o)
51
52
  except Exception:
52
- logger.exception(f'Failed to encode {obj}')
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, obj)
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, (list, tuple)):
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, (list, tuple)):
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
- markers = {}
105
- else:
106
- markers = None
107
- if self.ensure_ascii:
108
- _encoder = json.encoder.encode_basestring_ascii
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: {repr(o)}")
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 typing import Any, Callable, List, TypeVar, Union
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 marshmallow_dataclass import NewType
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
- DecoratedCallable = TypeVar("DecoratedCallable", bound=Union[Callable[..., Any], APIHTTPEndpoint])
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 = NewType('UUID', uuid.UUID, field=mf.UUID)
13
- DelimitedListUUID = NewType('DelimitedListUUID', List[uuid.UUID], field=sf.DelimitedListUUID)
14
- DelimitedListStr = NewType('DelimitedListStr', List[str], field=sf.DelimitedListStr)
15
- DelimitedListInt = NewType('DelimitedListInt', List[int], field=sf.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 = NewType("HttpUrl", str, field=sf.HttpUrl)
21
+ HttpUrl = Annotated[str, sf.HttpUrl]
18
22
 
19
23
  # Aliases
20
24
  UUIDType = UUID