starmallow 0.6.4__py3-none-any.whl → 0.7.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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.6.4"
1
+ __version__ = "0.7.0"
2
2
 
3
3
  from .applications import StarMallow
4
4
  from .exceptions import RequestValidationError
starmallow/background.py CHANGED
@@ -18,7 +18,7 @@ class BackgroundTask(StarletteBackgroundTask):
18
18
  else:
19
19
  await run_in_threadpool(self.func, *self.args, **self.kwargs)
20
20
  except BaseException as e:
21
- logger.exception(f'Background Task {self.func} failed: {e}')
21
+ logger.exception(f'Background Task {self.func.__module__}.{self.func.__name__} failed: {e}')
22
22
 
23
23
 
24
24
  class BackgroundTasks(StarletteBackgroundTasks):
@@ -14,6 +14,7 @@ from apispec.ext.marshmallow.openapi import OpenAPIConverter as ApiSpecOpenAPICo
14
14
  from marshmallow.utils import is_collection
15
15
  from packaging.version import Version
16
16
 
17
+ from starmallow.union_field import Union as UnionField
17
18
  from starmallow.utils import MARSHMALLOW_ITERABLES
18
19
 
19
20
  # marshmallow field => (JSON Schema type, format)
@@ -98,6 +99,7 @@ class OpenAPIConverter(ApiSpecOpenAPIConverter):
98
99
  self.add_attribute_function(self.field2title)
99
100
  self.add_attribute_function(self.field2uniqueItems)
100
101
  self.add_attribute_function(self.field2enum)
102
+ self.add_attribute_function(self.field2union)
101
103
 
102
104
  # Overriding to add exclusiveMinimum and exclusiveMaximum support
103
105
  def field2range(self: FieldConverterMixin, field: mf.Field, ret) -> dict:
@@ -211,6 +213,35 @@ class OpenAPIConverter(ApiSpecOpenAPIConverter):
211
213
 
212
214
  return ret
213
215
 
216
+ def field2union(self: FieldConverterMixin, field: mf.Field, **kwargs: Any) -> dict:
217
+ ret = {}
218
+
219
+ if isinstance(field, UnionField):
220
+ union_types = []
221
+ untyped = False
222
+ for _, subfield in field.union_fields:
223
+ for field_class in type(subfield).__mro__:
224
+ if field_class in self.field_mapping:
225
+ type_, fmt = self.field_mapping[field_class]
226
+
227
+ union_type = {}
228
+ if type_:
229
+ union_type['type'] = type_
230
+ if fmt:
231
+ union_type['fmt'] = fmt
232
+
233
+ if union_type:
234
+ union_types.append(union_type)
235
+ else:
236
+ # at least one untyped, so can't reliably create a schema
237
+ untyped = True
238
+ break
239
+
240
+ if union_types and not untyped:
241
+ ret['type'] = {'oneOf': union_types}
242
+
243
+ return ret
244
+
214
245
  # Overrice to add 'deprecated' support
215
246
  def _field2parameter(
216
247
  self, field: mf.Field, *, name: str, location: str,
@@ -0,0 +1,86 @@
1
+ '''Copied from marshmallow_dataclass, https://github.com/lovasoa/marshmallow_dataclass/blob/master/marshmallow_dataclass/union_field.py
2
+ Didn't want to add the dependency to this project1
3
+ '''
4
+
5
+ import copy
6
+ import inspect
7
+ from typing import Any, List, Optional, Tuple
8
+
9
+ import typeguard
10
+ from marshmallow import Schema, ValidationError, fields
11
+
12
+ try:
13
+ from typeguard import TypeCheckError # type: ignore[attr-defined]
14
+ except ImportError:
15
+ # typeguard < 3
16
+ TypeCheckError = TypeError # type: ignore[misc, assignment]
17
+
18
+ if "argname" not in inspect.signature(typeguard.check_type).parameters:
19
+
20
+ def _check_type(value, expected_type, argname: str):
21
+ return typeguard.check_type(value=value, expected_type=expected_type)
22
+
23
+ else:
24
+ # typeguard < 3.0.0rc2
25
+ def _check_type(value, expected_type, argname: str):
26
+ return typeguard.check_type( # type: ignore[call-overload]
27
+ value=value, expected_type=expected_type, argname=argname,
28
+ )
29
+
30
+
31
+ class Union(fields.Field):
32
+ """A union field, composed other `Field` classes or instances.
33
+ This field serializes elements based on their type, with one of its child fields.
34
+
35
+ Example: ::
36
+
37
+ number_or_string = UnionField([
38
+ (float, fields.Float()),
39
+ (str, fields.Str())
40
+ ])
41
+
42
+ :param union_fields: A list of types and their associated field instance.
43
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
44
+ """
45
+
46
+ def __init__(self, union_fields: List[Tuple[type, fields.Field]], **kwargs):
47
+ super().__init__(**kwargs)
48
+ self.union_fields = union_fields
49
+
50
+ def _bind_to_schema(self, field_name: str, schema: Schema) -> None:
51
+ super()._bind_to_schema(field_name, schema)
52
+ new_union_fields = []
53
+ for typ, field in self.union_fields:
54
+ field = copy.deepcopy(field)
55
+ field._bind_to_schema(field_name, self)
56
+ new_union_fields.append((typ, field))
57
+
58
+ self.union_fields = new_union_fields
59
+
60
+ def _serialize(self, value: Any, attr: Optional[str], obj, **kwargs) -> Any:
61
+ errors = []
62
+ if value is None:
63
+ return value
64
+ for typ, field in self.union_fields:
65
+ try:
66
+ _check_type(value=value, expected_type=typ, argname=attr or "anonymous")
67
+ return field._serialize(value, attr, obj, **kwargs)
68
+ except TypeCheckError as e:
69
+ errors.append(e)
70
+ raise TypeError(
71
+ f"Unable to serialize value with any of the fields in the union: {errors}",
72
+ )
73
+
74
+ def _deserialize(self, value: Any, attr: Optional[str], data, **kwargs) -> Any:
75
+ errors = []
76
+ for typ, field in self.union_fields:
77
+ try:
78
+ result = field.deserialize(value, **kwargs)
79
+ _check_type(
80
+ value=result, expected_type=typ, argname=attr or "anonymous",
81
+ )
82
+ return result
83
+ except (TypeCheckError, ValidationError) as e:
84
+ errors.append(e)
85
+
86
+ raise ValidationError(errors)
starmallow/utils.py CHANGED
@@ -34,12 +34,14 @@ import dpath.util
34
34
  import marshmallow as ma
35
35
  import marshmallow.fields as mf
36
36
  import marshmallow_dataclass.collection_field as collection_field
37
+ import typing_inspect
37
38
  from marshmallow.validate import Equal, OneOf
38
39
  from starlette.responses import Response
39
40
  from typing_inspect import is_final_type, is_generic_type, is_literal_type
40
41
 
41
42
  from starmallow.concurrency import contextmanager_in_threadpool
42
43
  from starmallow.datastructures import DefaultPlaceholder, DefaultType
44
+ from starmallow.union_field import Union as UnionField
43
45
 
44
46
  if TYPE_CHECKING: # pragma: nocover
45
47
  from starmallow.routing import APIRoute
@@ -136,6 +138,25 @@ def get_model_field(model: Any, **kwargs) -> mf.Field:
136
138
  if not is_generic_type(model) and lenient_issubclass(model, Enum):
137
139
  return mf.Enum(model, **kwargs)
138
140
 
141
+ # Union
142
+ if typing_inspect.is_union_type(model):
143
+ if typing_inspect.is_optional_type(model):
144
+ kwargs["allow_none"] = kwargs.get("allow_none", True)
145
+ kwargs["dump_default"] = kwargs.get("dump_default", None)
146
+ if not kwargs.get("required"):
147
+ kwargs["load_default"] = kwargs.get("load_default", None)
148
+ kwargs.setdefault("required", False)
149
+
150
+ arguments = get_args(model)
151
+ subtypes = [t for t in arguments if t is not NoneType] # type: ignore
152
+ if len(subtypes) == 1:
153
+ return get_model_field(model, **kwargs)
154
+
155
+ return UnionField(
156
+ [(subtyp, get_model_field(subtyp, required=True)) for subtyp in subtypes],
157
+ **kwargs
158
+ )
159
+
139
160
  origin = get_origin(model)
140
161
  if origin not in PY_ITERABLES:
141
162
  raise Exception(f'Unknown model type, model is {model}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: starmallow
3
- Version: 0.6.4
3
+ Version: 0.7.0
4
4
  Summary: StarMallow framework
5
5
  Project-URL: Homepage, https://github.com/mvanderlee/starmallow
6
6
  Author-email: Michiel Vanderlee <jmt.vanderlee@gmail.com>
@@ -1,6 +1,6 @@
1
- starmallow/__init__.py,sha256=avcrSswIDeCwzFtNce-PLnutuABlAJ3YWyTrKOtpZTE,322
1
+ starmallow/__init__.py,sha256=9E5AxUkYZG-q-eDh8OMP4YBFBCKro7Lp7hn2VFQe5E8,322
2
2
  starmallow/applications.py,sha256=mSL4YDozP8n6v22g4NX7EAMXmGhzzhtjtZd68YHcFvw,31720
3
- starmallow/background.py,sha256=SaFUFxZZhB5NCtOhhhOIsyaYx8uC7gkupUQtpxGSogI,963
3
+ starmallow/background.py,sha256=qxT6-9SfnkcGZzvecNOpIsCOMW0TPwDtpQ81GHI28P0,995
4
4
  starmallow/concurrency.py,sha256=MVRjo4Vqss_yqhaoeVt3xb7rLaSuAq_q9uYgTwbsojE,1375
5
5
  starmallow/constants.py,sha256=u0h8cJKhJY0oIZqzr7wpEZG2bPLrw5FroMnn3d8KBNQ,129
6
6
  starmallow/dataclasses.py,sha256=ap9DInvQjH2AyI4MAAnbDEuNnbPb94PigaNmEb7AQU8,2658
@@ -21,11 +21,12 @@ starmallow/routing.py,sha256=VSotmrEerVzuUfn20mpmSbuRVS4XiHrPtNRvBP8KJ4M,45397
21
21
  starmallow/schema_generator.py,sha256=yi368FwF9B50ZHSNOG0rvYVirVUeMFq2kXkUDeJUz4w,17961
22
22
  starmallow/serializers.py,sha256=rBEKMNgONgz_bai12uDvAEMCI_aEFGsqMSeIoWtlrOI,12514
23
23
  starmallow/types.py,sha256=8GXWjvzXQhF5NMHf14fbid6uErxVd1Xk_w2I4FoUgZ4,717
24
- starmallow/utils.py,sha256=WHw3MhCy1McGLEuhIDveH0NW8zpdyxSuvjVVx7NiAkU,11453
24
+ starmallow/union_field.py,sha256=psc5GPWDGVeu4vxFf2ItgtHEK8tT62IZHrxoRZidyxc,3113
25
+ starmallow/utils.py,sha256=ilJyQHXZq1o6XRaDdninTNTt4u4KzIPQwImbhNQeZUs,12285
25
26
  starmallow/websockets.py,sha256=yIz3LzTBMNclpEoG7oTMbQwxbcdKNU6M8XcqZMyBTuA,2223
26
27
  starmallow/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
28
  starmallow/ext/marshmallow/__init__.py,sha256=33jENGdfPq4-CDG0LOmN3KOGW1pXTy7a2oMwy4hrYzM,208
28
- starmallow/ext/marshmallow/openapi.py,sha256=5aGvbwLGVucsVhXExpYeyt8n5dQTzazrf-nuh6mVhmA,9017
29
+ starmallow/ext/marshmallow/openapi.py,sha256=BOPgTmolpZxQaPvyg0VcHLTjWSlvEj2L1OOHGFKfWik,10189
29
30
  starmallow/middleware/__init__.py,sha256=vtNm85Z9pUPjJd-9giJGg3YL1wO7Jm5ooXBm31pDOK8,53
30
31
  starmallow/middleware/asyncexitstack.py,sha256=0GPhQSxqSVmAiVIqBIN5slueWYZ8bwh9f2bBPy7AbP0,1191
31
32
  starmallow/security/__init__.py,sha256=1rQFBIGnEbE51XDZSSi9NgPjXLScFq3RoLu4vk0KVYw,191
@@ -35,7 +36,7 @@ starmallow/security/http.py,sha256=cpGjM1kFDq3i_bOY96kMkf4cspBUxFkkET9lTK3NA-0,6
35
36
  starmallow/security/oauth2.py,sha256=1nv1580PY4cwgu5gzpQCf2MfMNv2Cfv05753AUHPOhQ,10005
36
37
  starmallow/security/open_id_connect_url.py,sha256=IPsL2YzWc2mPwJbrUn6oFRTi7uRAG6mR62CGwmzBs1k,1399
37
38
  starmallow/security/utils.py,sha256=bd8T0YM7UQD5ATKucr1bNtAvz_Y3__dVNAv5UebiPvc,293
38
- starmallow-0.6.4.dist-info/METADATA,sha256=6L5z-Ws64Ssrac9s0YES4-vO1bySyMsKDHV6OEDbEek,5615
39
- starmallow-0.6.4.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
40
- starmallow-0.6.4.dist-info/licenses/LICENSE.md,sha256=QelyGgOzch8CXzy6HrYwHh7nmj0rlWkDA0YzmZ3CPaY,1084
41
- starmallow-0.6.4.dist-info/RECORD,,
39
+ starmallow-0.7.0.dist-info/METADATA,sha256=nfu-fi46UGqJcsgp88z9WTm_D1qepTVczqHGdkb-a_w,5615
40
+ starmallow-0.7.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
41
+ starmallow-0.7.0.dist-info/licenses/LICENSE.md,sha256=QelyGgOzch8CXzy6HrYwHh7nmj0rlWkDA0YzmZ3CPaY,1084
42
+ starmallow-0.7.0.dist-info/RECORD,,