starmallow 0.6.5__tar.gz → 0.8.0__tar.gz

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.
Files changed (117) hide show
  1. {starmallow-0.6.5 → starmallow-0.8.0}/PKG-INFO +4 -4
  2. {starmallow-0.6.5 → starmallow-0.8.0}/README.md +2 -2
  3. {starmallow-0.6.5 → starmallow-0.8.0}/examples/cache_server.py +1 -1
  4. {starmallow-0.6.5 → starmallow-0.8.0}/examples/flask_server.py +1 -1
  5. {starmallow-0.6.5 → starmallow-0.8.0}/examples/goals.ipynb +2 -2
  6. {starmallow-0.6.5 → starmallow-0.8.0}/examples/sample_server.py +1 -1
  7. {starmallow-0.6.5 → starmallow-0.8.0}/pyproject.toml +1 -1
  8. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/__init__.py +1 -1
  9. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/endpoint.py +4 -0
  10. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/ext/marshmallow/openapi.py +32 -1
  11. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/api_key.py +1 -1
  12. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/base.py +1 -1
  13. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/http.py +1 -1
  14. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/oauth2.py +1 -1
  15. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/open_id_connect_url.py +1 -1
  16. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/types.py +1 -1
  17. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/utils.py +26 -1
  18. {starmallow-0.6.5 → starmallow-0.8.0}/tests/basic_api.py +6 -1
  19. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_cookie.py +1 -1
  20. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_cookie_description.py +1 -1
  21. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_cookie_optional.py +1 -1
  22. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_header.py +1 -1
  23. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_header_description.py +1 -1
  24. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_header_optional.py +1 -1
  25. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_query.py +1 -1
  26. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_query_description.py +1 -1
  27. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/test_api_key_query_optional.py +1 -1
  28. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2.py +1 -1
  29. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_optional.py +1 -1
  30. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_optional_description.py +1 -1
  31. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/openid_connect/test_openid_connect.py +1 -1
  32. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/openid_connect/test_openid_connect_description.py +1 -1
  33. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/openid_connect/test_openid_connect_optional.py +1 -1
  34. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_properties.py +1 -1
  35. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_custom_model_in_callback.py +1 -1
  36. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_custom_validationerror.py +1 -1
  37. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_response_class.py +1 -1
  38. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_router.py +1 -1
  39. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_annotated.py +1 -1
  40. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_basic_api.py +49 -1
  41. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_dataclass_fields.py +1 -1
  42. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_delimited_params.py +1 -0
  43. starmallow-0.8.0/tests/test_generics.py +149 -0
  44. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_input.py +1 -1
  45. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_requests_orjson.py +1 -1
  46. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_requests_ujson.py +1 -1
  47. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_responses.py +1 -1
  48. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_responses_orjson.py +1 -1
  49. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_responses_ujson.py +1 -1
  50. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_ws_router.py +1 -1
  51. {starmallow-0.6.5 → starmallow-0.8.0}/.editorconfig +0 -0
  52. {starmallow-0.6.5 → starmallow-0.8.0}/.gitignore +0 -0
  53. {starmallow-0.6.5 → starmallow-0.8.0}/.pre-commit-config.yaml +0 -0
  54. {starmallow-0.6.5 → starmallow-0.8.0}/Dockerfile +0 -0
  55. {starmallow-0.6.5 → starmallow-0.8.0}/LICENSE.md +0 -0
  56. {starmallow-0.6.5 → starmallow-0.8.0}/docker-compose.yml +0 -0
  57. {starmallow-0.6.5 → starmallow-0.8.0}/docs/design_ideas.md +0 -0
  58. {starmallow-0.6.5 → starmallow-0.8.0}/examples/__init__.py +0 -0
  59. {starmallow-0.6.5 → starmallow-0.8.0}/examples/gunicorn.py +0 -0
  60. {starmallow-0.6.5 → starmallow-0.8.0}/examples/recommended_server.py +0 -0
  61. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/applications.py +0 -0
  62. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/background.py +0 -0
  63. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/concurrency.py +0 -0
  64. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/constants.py +0 -0
  65. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/dataclasses.py +0 -0
  66. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/datastructures.py +0 -0
  67. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/decorators.py +0 -0
  68. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/delimited_field.py +0 -0
  69. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/docs.py +0 -0
  70. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/endpoints.py +0 -0
  71. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/exception_handlers.py +0 -0
  72. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/exceptions.py +0 -0
  73. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/ext/__init__.py +0 -0
  74. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/ext/marshmallow/__init__.py +0 -0
  75. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/fields.py +0 -0
  76. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/middleware/__init__.py +0 -0
  77. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/middleware/asyncexitstack.py +0 -0
  78. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/params.py +0 -0
  79. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/request_resolver.py +0 -0
  80. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/requests.py +0 -0
  81. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/responses.py +0 -0
  82. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/routing.py +0 -0
  83. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/schema_generator.py +0 -0
  84. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/__init__.py +0 -0
  85. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/security/utils.py +0 -0
  86. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/serializers.py +0 -0
  87. {starmallow-0.6.5 → starmallow-0.8.0}/starmallow/websockets.py +0 -0
  88. {starmallow-0.6.5 → starmallow-0.8.0}/tests/__init__.py +0 -0
  89. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/__init__.py +0 -0
  90. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/api_key/__init__.py +0 -0
  91. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/__init__.py +0 -0
  92. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_base.py +0 -0
  93. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_base_description.py +0 -0
  94. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_base_optional.py +0 -0
  95. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_basic.py +0 -0
  96. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_basic_realm.py +0 -0
  97. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_basic_realm_description.py +0 -0
  98. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_bearer.py +0 -0
  99. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_bearer_description.py +0 -0
  100. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_bearer_optional.py +0 -0
  101. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_digest.py +0 -0
  102. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_digest_description.py +0 -0
  103. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/http/test_http_digest_optional.py +0 -0
  104. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/__init__.py +0 -0
  105. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_authorization_code_bearer.py +0 -0
  106. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_authorization_code_bearer_description.py +0 -0
  107. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_password_bearer_optional.py +0 -0
  108. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/oauth2/test_oauth2_password_bearer_optional_description.py +0 -0
  109. {starmallow-0.6.5 → starmallow-0.8.0}/tests/security/openid_connect/__init__.py +0 -0
  110. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_response_extra.py +0 -0
  111. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_bad.py +0 -0
  112. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_additional_responses_default_validationerror.py +0 -0
  113. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_http_endpoints.py +0 -0
  114. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_middleware.py +0 -0
  115. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_resolved_param_contextmanagers.py +0 -0
  116. {starmallow-0.6.5 → starmallow-0.8.0}/tests/test_resolved_params.py +0 -0
  117. {starmallow-0.6.5 → starmallow-0.8.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: starmallow
3
- Version: 0.6.5
3
+ Version: 0.8.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>
@@ -28,7 +28,7 @@ Classifier: Typing :: Typed
28
28
  Requires-Python: >=3.10
29
29
  Requires-Dist: apispec[marshmallow]<7,>=6
30
30
  Requires-Dist: dpath<3,>=2.1.0
31
- Requires-Dist: marshmallow-dataclass<9,>=8.5.1
31
+ Requires-Dist: marshmallow-dataclass2<9,>=8.8.1
32
32
  Requires-Dist: marshmallow<4,>=3.18.0
33
33
  Requires-Dist: python-multipart<0.0.7,>=0.0.5
34
34
  Requires-Dist: pyyaml>=5.4.1
@@ -68,7 +68,7 @@ Create a file `main.py` with:
68
68
 
69
69
  ```python
70
70
  from typing import Annotated
71
- from marshmallow_dataclass import dataclass
71
+ from marshmallow_dataclass2 import dataclass
72
72
  from starmallow import Body, Path, StarMallow
73
73
 
74
74
  app = StarMallow()
@@ -131,7 +131,7 @@ INFO: Application startup complete.
131
131
  You can also use class-based views. This can make it easier to organize your code and gives you an easy migration path if you use [flask-smorest](https://flask-smorest.readthedocs.io/)
132
132
 
133
133
  ```python
134
- from marshmallow_dataclass import dataclass
134
+ from marshmallow_dataclass2 import dataclass
135
135
  from starmallow import StarMallow
136
136
  from starmallow.decorators import route
137
137
  from starmallow.endpoints import APIHTTPEndpoint
@@ -13,7 +13,7 @@ Create a file `main.py` with:
13
13
 
14
14
  ```python
15
15
  from typing import Annotated
16
- from marshmallow_dataclass import dataclass
16
+ from marshmallow_dataclass2 import dataclass
17
17
  from starmallow import Body, Path, StarMallow
18
18
 
19
19
  app = StarMallow()
@@ -76,7 +76,7 @@ INFO: Application startup complete.
76
76
  You can also use class-based views. This can make it easier to organize your code and gives you an easy migration path if you use [flask-smorest](https://flask-smorest.readthedocs.io/)
77
77
 
78
78
  ```python
79
- from marshmallow_dataclass import dataclass
79
+ from marshmallow_dataclass2 import dataclass
80
80
  from starmallow import StarMallow
81
81
  from starmallow.decorators import route
82
82
  from starmallow.endpoints import APIHTTPEndpoint
@@ -5,7 +5,7 @@ import hashlib
5
5
  import marshmallow.fields as mf
6
6
  from brotli_asgi import BrotliMiddleware
7
7
  from marshmallow.validate import Range
8
- from marshmallow_dataclass import dataclass as ma_dataclass
8
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
9
9
  from starlette.middleware import Middleware
10
10
  from starlette_context import middleware, plugins
11
11
 
@@ -9,7 +9,7 @@ from flask import json as flask_json
9
9
  from flask.views import MethodView
10
10
  # from flask_compress import Compress
11
11
  from flask_smorest import Api, Blueprint
12
- from marshmallow_dataclass import dataclass as ma_dataclass
12
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
13
13
 
14
14
  default_flask_config = {
15
15
  'OPENAPI_VERSION': '3.0.3',
@@ -23,7 +23,7 @@
23
23
  "outputs": [],
24
24
  "source": [
25
25
  "import marshmallow.fields as mf\n",
26
- "from marshmallow_dataclass import dataclass as ma_dataclass\n",
26
+ "from marshmallow_dataclass2 import dataclass as ma_dataclass\n",
27
27
  "from starmallow import APIRouter\n",
28
28
  "from starmallow.params import (\n",
29
29
  " Header, \n",
@@ -150,7 +150,7 @@
150
150
  "from typing import Any, Callable, TypeVar, Generic, Optional, _SpecialForm\n",
151
151
  "\n",
152
152
  "import marshmallow.fields as mf\n",
153
- "from marshmallow_dataclass import dataclass as ma_dataclass\n",
153
+ "from marshmallow_dataclass2 import dataclass as ma_dataclass\n",
154
154
  "\n",
155
155
  "@ma_dataclass\n",
156
156
  "class CreateRequest:\n",
@@ -5,7 +5,7 @@ import aiofiles.tempfile
5
5
  import marshmallow.fields as mf
6
6
  import orjson
7
7
  from marshmallow.validate import Range
8
- from marshmallow_dataclass import dataclass as ma_dataclass
8
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
9
9
  from starlette.middleware import Middleware
10
10
  from starlette.middleware.gzip import GZipMiddleware
11
11
  from starlette_context import middleware, plugins
@@ -38,7 +38,7 @@ dependencies = [
38
38
  "apispec[marshmallow] >=6,<7",
39
39
  "dpath >=2.1.0,<3",
40
40
  "marshmallow >=3.18.0,<4",
41
- "marshmallow-dataclass >=8.5.1,<9",
41
+ "marshmallow-dataclass2 >=8.8.1,<9",
42
42
  "python-multipart >=0.0.5,<0.0.7",
43
43
  "pyyaml >=5.4.1",
44
44
  "starlette >=0.35,<1",
@@ -1,4 +1,4 @@
1
- __version__ = "0.6.5"
1
+ __version__ = "0.8.0"
2
2
 
3
3
  from .applications import StarMallow
4
4
  from .exceptions import RequestValidationError
@@ -20,6 +20,7 @@ from typing import (
20
20
  import marshmallow as ma
21
21
  import marshmallow.fields as mf
22
22
  from marshmallow.utils import missing as missing_
23
+ from marshmallow_dataclass2 import class_schema, is_generic_alias_of_dataclass
23
24
  from starlette.background import BackgroundTasks
24
25
  from starlette.requests import HTTPConnection, Request
25
26
  from starlette.responses import Response
@@ -243,6 +244,9 @@ class EndpointMixin:
243
244
  if is_marshmallow_dataclass(model):
244
245
  model = model.Schema
245
246
 
247
+ if is_generic_alias_of_dataclass(model):
248
+ model = class_schema(model)
249
+
246
250
  if isinstance(model, NewType) and getattr(model, '_marshmallow_field', None):
247
251
  return model._marshmallow_field(**kwargs)
248
252
  elif is_marshmallow_schema(model):
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  import marshmallow as ma
4
4
  import marshmallow.fields as mf
5
- import marshmallow_dataclass.collection_field as collection_field
5
+ import marshmallow_dataclass2.collection_field as collection_field
6
6
  from apispec import APISpec
7
7
  from apispec.ext.marshmallow.common import get_fields
8
8
  from apispec.ext.marshmallow.field_converter import (
@@ -12,6 +12,7 @@ from apispec.ext.marshmallow.field_converter import (
12
12
  )
13
13
  from apispec.ext.marshmallow.openapi import OpenAPIConverter as ApiSpecOpenAPIConverter
14
14
  from marshmallow.utils import is_collection
15
+ from marshmallow_dataclass2.union_field import Union as UnionField
15
16
  from packaging.version import Version
16
17
 
17
18
  from starmallow.utils import MARSHMALLOW_ITERABLES
@@ -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,
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
  from typing import ClassVar, Optional
3
3
 
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_403_FORBIDDEN
7
7
 
@@ -2,7 +2,7 @@ from enum import Enum
2
2
  from typing import Any, ClassVar, Dict, Optional
3
3
 
4
4
  import marshmallow as ma
5
- from marshmallow_dataclass import dataclass as ma_dataclass
5
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
6
6
 
7
7
 
8
8
  # Provided by: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object
@@ -2,7 +2,7 @@ import binascii
2
2
  from base64 import b64decode
3
3
  from typing import ClassVar, Optional
4
4
 
5
- from marshmallow_dataclass import dataclass as ma_dataclass
5
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
6
6
  from starlette.requests import Request
7
7
  from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
8
8
 
@@ -1,7 +1,7 @@
1
1
  from typing import Any, Dict, List, Optional, Union
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
 
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
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
@@ -2,7 +2,7 @@ import uuid
2
2
  from typing import Any, Callable, List, TypeVar, Union
3
3
 
4
4
  import marshmallow.fields as mf
5
- from marshmallow_dataclass import NewType
5
+ from marshmallow_dataclass2 import NewType
6
6
 
7
7
  import starmallow.fields as sf
8
8
  from starmallow.endpoints import APIHTTPEndpoint
@@ -33,8 +33,11 @@ from typing import (
33
33
  import dpath.util
34
34
  import marshmallow as ma
35
35
  import marshmallow.fields as mf
36
- import marshmallow_dataclass.collection_field as collection_field
36
+ import marshmallow_dataclass2.collection_field as collection_field
37
+ import typing_inspect
37
38
  from marshmallow.validate import Equal, OneOf
39
+ from marshmallow_dataclass2 import class_schema, is_generic_alias_of_dataclass
40
+ from marshmallow_dataclass2.union_field import Union as UnionField
38
41
  from starlette.responses import Response
39
42
  from typing_inspect import is_final_type, is_generic_type, is_literal_type
40
43
 
@@ -103,6 +106,9 @@ def get_model_field(model: Any, **kwargs) -> mf.Field:
103
106
  if is_marshmallow_dataclass(model):
104
107
  model = model.Schema
105
108
 
109
+ if is_generic_alias_of_dataclass(model):
110
+ model = class_schema(model)
111
+
106
112
  if is_marshmallow_schema(model):
107
113
  return mf.Nested(model if isinstance(model, ma.Schema) else model())
108
114
 
@@ -136,6 +142,25 @@ def get_model_field(model: Any, **kwargs) -> mf.Field:
136
142
  if not is_generic_type(model) and lenient_issubclass(model, Enum):
137
143
  return mf.Enum(model, **kwargs)
138
144
 
145
+ # Union
146
+ if typing_inspect.is_union_type(model):
147
+ if typing_inspect.is_optional_type(model):
148
+ kwargs["allow_none"] = kwargs.get("allow_none", True)
149
+ kwargs["dump_default"] = kwargs.get("dump_default", None)
150
+ if not kwargs.get("required"):
151
+ kwargs["load_default"] = kwargs.get("load_default", None)
152
+ kwargs.setdefault("required", False)
153
+
154
+ arguments = get_args(model)
155
+ subtypes = [t for t in arguments if t is not NoneType] # type: ignore
156
+ if len(subtypes) == 1:
157
+ return get_model_field(model, **kwargs)
158
+
159
+ return UnionField(
160
+ [(subtyp, get_model_field(subtyp, required=True)) for subtyp in subtypes],
161
+ **kwargs
162
+ )
163
+
139
164
  origin = get_origin(model)
140
165
  if origin not in PY_ITERABLES:
141
166
  raise Exception(f'Unknown model type, model is {model}')
@@ -6,7 +6,7 @@ from typing import Final, FrozenSet, Literal, Optional, Union
6
6
  from uuid import UUID
7
7
 
8
8
  from marshmallow.validate import Length, Range, Regexp
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
 
11
11
  from starmallow import Path, Query, StarMallow
12
12
 
@@ -116,6 +116,11 @@ def get_final_id(item_id: FinalItem = Path()):
116
116
  return item_id
117
117
 
118
118
 
119
+ @app.get("/path/union/{item_id}")
120
+ def get_union_id(item_id: int | float | bool):
121
+ return item_id
122
+
123
+
119
124
  #########################################################
120
125
  # Test Path parameters
121
126
  #########################################################
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.testclient import TestClient
5
5
 
6
6
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.testclient import TestClient
5
5
 
6
6
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.testclient import TestClient
5
5
 
6
6
  from starmallow import StarMallow
@@ -1,5 +1,5 @@
1
1
  import pytest
2
- from marshmallow_dataclass import dataclass as ma_dataclass
2
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
3
3
  from starlette.testclient import TestClient
4
4
 
5
5
  from starmallow import StarMallow
@@ -1,7 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  import pytest
4
- from marshmallow_dataclass import dataclass as ma_dataclass
4
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
5
5
  from starlette.testclient import TestClient
6
6
 
7
7
  from starmallow import StarMallow
@@ -1,7 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  import pytest
4
- from marshmallow_dataclass import dataclass as ma_dataclass
4
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
5
5
  from starlette.testclient import TestClient
6
6
 
7
7
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import StarMallow
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.testclient import TestClient
5
5
 
6
6
  from starmallow import StarMallow
@@ -1,6 +1,6 @@
1
1
  from typing import Dict
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.testclient import TestClient
5
5
 
6
6
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.responses import JSONResponse
3
3
  from starlette.testclient import TestClient
4
4
 
@@ -1,6 +1,6 @@
1
1
  import typing
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.responses import JSONResponse
5
5
  from starlette.testclient import TestClient
6
6
 
@@ -1,6 +1,6 @@
1
1
  import typing
2
2
 
3
- from marshmallow_dataclass import dataclass as ma_dataclass
3
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
4
4
  from starlette.responses import JSONResponse
5
5
  from starlette.testclient import TestClient
6
6
 
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
 
4
4
  from starmallow import APIRouter, StarMallow
@@ -5,7 +5,7 @@ from typing import Annotated, Literal, Optional, Union
5
5
  import marshmallow as ma
6
6
  import marshmallow.fields as mf
7
7
  import pytest
8
- from marshmallow_dataclass import dataclass as ma_dataclass
8
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
9
9
  from starlette.testclient import TestClient
10
10
 
11
11
  from starmallow import Body, Header, Path, Query, ResolvedParam, StarMallow
@@ -470,6 +470,51 @@ openapi_schema = {
470
470
  ],
471
471
  },
472
472
  },
473
+ "/path/union/{item_id}": {
474
+ "get": {
475
+ "responses": {
476
+ "200": {
477
+ "description": "Successful Response",
478
+ "content": {"application/json": {"schema": {}}},
479
+ },
480
+ "422": {
481
+ "description": "Validation Error",
482
+ "content": {
483
+ "application/json": {
484
+ "schema": {
485
+ "$ref": "#/components/schemas/HTTPValidationError",
486
+ },
487
+ },
488
+ },
489
+ },
490
+ },
491
+ "summary": "Get Union Id",
492
+ "operationId": "get_union_id_path_union__item_id__get",
493
+ "parameters": [
494
+ {
495
+ 'in': 'path',
496
+ 'name': 'item_id',
497
+ 'required': True,
498
+ 'schema': {
499
+ 'title': 'Item Id',
500
+ "type": {
501
+ "oneOf": [
502
+ {
503
+ "type": "integer",
504
+ },
505
+ {
506
+ "type": "number",
507
+ },
508
+ {
509
+ "type": "boolean",
510
+ },
511
+ ],
512
+ },
513
+ },
514
+ },
515
+ ],
516
+ },
517
+ },
473
518
  "/path/param/{item_id}": {
474
519
  "get": {
475
520
  "responses": {
@@ -2259,7 +2304,10 @@ openapi_schema = {
2259
2304
  "path,expected_status,expected_response",
2260
2305
  [
2261
2306
  ("/api_route", 200, {"message": "Hello World"}),
2262
- ("/non_decorated_route", 200, {"message": "Hello World"}),
2307
+ ("/path/union/500.0", 200, 500.0),
2308
+ ("/path/union/200", 200, 200),
2309
+ ("/path/union/true", 200, True),
2310
+ ("/api_route", 200, {"message": "Hello World"}),
2263
2311
  ("/nonexistent", 404, {"detail": "Not Found"}),
2264
2312
  ("/openapi.json", 200, openapi_schema),
2265
2313
  ],
@@ -4,7 +4,7 @@
4
4
  from typing import List
5
5
 
6
6
  import pytest
7
- from marshmallow_dataclass import dataclass as ma_dataclass
7
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
8
8
  from starlette.testclient import TestClient
9
9
 
10
10
  from starmallow import Body, StarMallow
@@ -235,6 +235,7 @@ openapi_schema = {
235
235
  [
236
236
  ("/path/1,2,3,4", {}, 200, [1, 2, 3, 4]),
237
237
  ("/query?item_ids=5,4,3,2", {}, 200, [5, 4, 3, 2]),
238
+ ("/query?item_ids=", {}, 200, []),
238
239
  ("/header", {'item_ids': '6,8,7'}, 200, [6, 8, 7]),
239
240
  ("/openapi.json", {}, 200, openapi_schema),
240
241
  ],
@@ -0,0 +1,149 @@
1
+ '''Test Resolved Params'''
2
+ from dataclasses import field
3
+ from typing import Annotated, Generic, TypeVar
4
+
5
+ import pytest
6
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
7
+ from starlette.testclient import TestClient
8
+
9
+ from starmallow import Query, StarMallow
10
+
11
+ from .utils import assert_json
12
+
13
+ app = StarMallow()
14
+
15
+ T = TypeVar("T")
16
+
17
+ ############################################################
18
+ # Models - classes and schemas
19
+ ############################################################
20
+ # region - VS Code folding marker - https://code.visualstudio.com/docs/editor/codebasics#_folding
21
+
22
+
23
+ @ma_dataclass
24
+ class QueryParameters(Generic[T]):
25
+ id: T = None
26
+
27
+
28
+ @ma_dataclass
29
+ class PageableResponse(Generic[T]):
30
+ items: list[T] = field(default_factory=list)
31
+ # endregion
32
+
33
+
34
+ ############################################################
35
+ # Test API
36
+ ############################################################
37
+ # region
38
+ @app.get("/data")
39
+ def get_data(
40
+ params: Annotated[QueryParameters[int], Query()],
41
+ ) -> PageableResponse[str]:
42
+ return {'items': ['foo', 'bar']}
43
+ # endregion
44
+
45
+
46
+ ############################################################
47
+ # Tests
48
+ ############################################################
49
+ # region
50
+ client = TestClient(app)
51
+
52
+ openapi_schema = {
53
+ "openapi": "3.0.2",
54
+ "info": {"title": "StarMallow", "version": "0.1.0"},
55
+ "paths": {
56
+ "/data": {
57
+ "get": {
58
+ "responses": {
59
+ "200": {
60
+ "description": "Successful Response",
61
+ "content": {
62
+ "application/json": {
63
+ "schema": {
64
+ "$ref": "#/components/schemas/PageableResponse",
65
+ },
66
+ },
67
+ },
68
+ },
69
+ "422": {
70
+ "description": "Validation Error",
71
+ "content": {
72
+ "application/json": {
73
+ "schema": {
74
+ "$ref": "#/components/schemas/HTTPValidationError",
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ "summary": "Get Data",
81
+ "operationId": "get_data_data_get",
82
+ "parameters": [
83
+ {
84
+ "required": False,
85
+ "schema": {
86
+ "default": None,
87
+ "nullable": True,
88
+ "type": "integer",
89
+ "title": "Id",
90
+ },
91
+ "name": "id",
92
+ "in": "query",
93
+ },
94
+ ],
95
+ },
96
+ },
97
+ },
98
+ "components": {
99
+ "schemas": {
100
+ "HTTPValidationError": {
101
+ 'properties': {
102
+ 'detail': {
103
+ 'description': 'Error detail',
104
+ 'title': 'Detail',
105
+ },
106
+ 'errors': {
107
+ 'description': 'Exception or error type',
108
+ 'title': 'Errors',
109
+ },
110
+ 'status_code': {
111
+ 'description': 'HTTP status code',
112
+ 'title': 'Status Code',
113
+ 'type': 'integer',
114
+ },
115
+ },
116
+ 'required': ['detail', 'status_code'],
117
+ 'title': 'HTTPValidationError',
118
+ 'type': 'object',
119
+ },
120
+ "PageableResponse": {
121
+ "properties": {
122
+ "items": {
123
+ "items": {
124
+ "type": "string",
125
+ },
126
+ "title": "Items",
127
+ "type": "array",
128
+ },
129
+ },
130
+ "title": "PageableResponse",
131
+ "type": "object",
132
+ },
133
+ },
134
+ },
135
+ }
136
+
137
+
138
+ @pytest.mark.parametrize(
139
+ "path,expected_status,expected_response",
140
+ [
141
+ ("/data?id=50", 200, {"items": ["foo", "bar"]}),
142
+ ("/openapi.json", 200, openapi_schema),
143
+ ],
144
+ )
145
+ def test_get_path(path, expected_status, expected_response):
146
+ response = client.get(path)
147
+ assert response.status_code == expected_status
148
+ assert_json(response.json(), expected_response)
149
+ # endregion
@@ -7,7 +7,7 @@ from typing import Any, Dict, Literal, Optional, Union
7
7
  import marshmallow as ma
8
8
  import marshmallow.fields as mf
9
9
  import pytest
10
- from marshmallow_dataclass import dataclass as ma_dataclass
10
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
11
11
  from starlette.background import BackgroundTasks
12
12
  from starlette.requests import HTTPConnection, Request
13
13
  from starlette.responses import Response
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
 
7
7
  import marshmallow.fields as mf
8
8
  import pytest
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
  from starlette.testclient import TestClient
11
11
 
12
12
  from starmallow import Body, StarMallow
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
 
7
7
  import marshmallow.fields as mf
8
8
  import pytest
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
  from starlette.testclient import TestClient
11
11
 
12
12
  from starmallow import Body, StarMallow
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
 
7
7
  import marshmallow.fields as mf
8
8
  import pytest
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
  from starlette.testclient import TestClient
11
11
 
12
12
  from starmallow import StarMallow
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
 
7
7
  import marshmallow.fields as mf
8
8
  import pytest
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
  from starlette.testclient import TestClient
11
11
 
12
12
  from starmallow import StarMallow
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
 
7
7
  import marshmallow.fields as mf
8
8
  import pytest
9
- from marshmallow_dataclass import dataclass as ma_dataclass
9
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
10
10
  from starlette.testclient import TestClient
11
11
 
12
12
  from starmallow import StarMallow
@@ -1,4 +1,4 @@
1
- from marshmallow_dataclass import dataclass as ma_dataclass
1
+ from marshmallow_dataclass2 import dataclass as ma_dataclass
2
2
  from starlette.testclient import TestClient
3
3
  from starlette.websockets import WebSocket
4
4
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes