voluptuous-openapi 0.0.1__tar.gz → 0.0.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: voluptuous-openapi
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Convert voluptuous schemas to OpenAPI Schema object
5
5
  Home-page: http://github.com/Shulyaka/voluptuous-openapi
6
6
  Author: Denis Shulyaka
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name="voluptuous-openapi",
5
- version="0.0.1",
5
+ version="0.0.2",
6
6
  description="Convert voluptuous schemas to OpenAPI Schema object",
7
7
  url="http://github.com/Shulyaka/voluptuous-openapi",
8
8
  author="Denis Shulyaka",
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
3
  import voluptuous as vol
4
+ from typing import Any, TypeVar
4
5
 
5
6
  from voluptuous_openapi import UNSUPPORTED, convert
6
7
 
@@ -191,6 +192,7 @@ def test_custom_serializer():
191
192
  def test_constant():
192
193
  for value in True, False, "Hello", 1, None:
193
194
  assert {"enum": [value]} == convert(vol.Schema(value))
195
+ assert {"enum": [None]} == convert(vol.Schema(type(None)))
194
196
 
195
197
 
196
198
  def test_enum():
@@ -207,6 +209,20 @@ def test_list():
207
209
  "items": {"type": "string"},
208
210
  } == convert(vol.Schema([str]))
209
211
 
212
+ assert {"type": "array", "items": {"type": "string"}} == convert(vol.Schema(list))
213
+
214
+
215
+ def test_any_of():
216
+ assert {"anyOf": [{"type": "number"}, {"type": "integer"}]} == convert(
217
+ vol.Any(float, int)
218
+ )
219
+
220
+
221
+ def test_all_of():
222
+ assert {"allOf": [{"minimum": 5}, {"minimum": 10}]} == convert(
223
+ vol.All(vol.Range(min=5), vol.Range(min=10))
224
+ )
225
+
210
226
 
211
227
  def test_key_any():
212
228
  assert {
@@ -223,3 +239,94 @@ def test_key_any():
223
239
  },
224
240
  "required": [],
225
241
  } == convert(vol.Schema({vol.Any("name", "area"): str}))
242
+
243
+
244
+ def test_function():
245
+ def validator(data):
246
+ return data
247
+
248
+ assert {
249
+ "type": "object",
250
+ "properties": {"test_data": {"type": "string"}},
251
+ "required": [],
252
+ } == convert(vol.Schema({"test_data": validator}))
253
+
254
+ def validator_str(data: str):
255
+ return data
256
+
257
+ assert {"type": "string"} == convert(vol.Schema(validator_str))
258
+
259
+ def validator_any(data: Any):
260
+ return data
261
+
262
+ assert {} == convert(validator_any)
263
+ assert {"type": "integer"} == convert(vol.All(vol.Coerce(int), lambda x: x / 100))
264
+
265
+ def validator_nullable(data: float | None):
266
+ return data
267
+
268
+ assert {"type": "number", "nullable": True} == convert(
269
+ vol.Schema(validator_nullable)
270
+ )
271
+
272
+ def validator_union(data: float | int):
273
+ return data
274
+
275
+ assert {"anyOf": [{"type": "number"}, {"type": "integer"}]} == convert(
276
+ vol.Schema(validator_union)
277
+ )
278
+
279
+ _T = TypeVar("_T")
280
+
281
+ def validator_nullable_2(value: _T | None):
282
+ return value
283
+
284
+ assert {
285
+ "type": "object",
286
+ "properties": {"var": {"type": "array", "items": {"type": "string"}}},
287
+ "required": [],
288
+ } == convert(vol.Schema({"var": vol.All(validator_nullable_2, [validator_any])}))
289
+
290
+ def validator_list_int(value: list[int]):
291
+ return value
292
+
293
+ assert {"type": "array", "items": {"type": "integer"}} == convert(
294
+ validator_list_int
295
+ )
296
+
297
+ def validator_list_any(value: list[Any]):
298
+ return value
299
+
300
+ assert {"type": "array", "items": {"type": "string"}} == convert(validator_list_any)
301
+
302
+ def validator_list(value: list):
303
+ return value
304
+
305
+ assert {"type": "array", "items": {"type": "string"}} == convert(validator_list)
306
+
307
+ def validator_set_int(value: set[int]):
308
+ return value
309
+
310
+ assert {"type": "array", "items": {"type": "integer"}} == convert(validator_set_int)
311
+
312
+ def validator_set_any(value: set[Any]):
313
+ return value
314
+
315
+ assert {"type": "array", "items": {"type": "string"}} == convert(validator_set_any)
316
+
317
+ def validator_set(value: set):
318
+ return value
319
+
320
+ assert {"type": "array", "items": {"type": "string"}} == convert(validator_set)
321
+
322
+ def validator_dict(value: dict):
323
+ return value
324
+
325
+ assert {"type": "object", "additionalProperties": True} == convert(validator_dict)
326
+
327
+ def validator_dict_int(value: dict[str, int]):
328
+ return value
329
+
330
+ assert {"type": "object", "additionalProperties": {"type": "integer"}} == convert(
331
+ validator_dict_int
332
+ )
@@ -2,7 +2,9 @@
2
2
 
3
3
  from collections.abc import Callable, Mapping, Sequence
4
4
  from enum import Enum
5
- from typing import Any
5
+ from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
6
+ from types import NoneType, UnionType
7
+ from inspect import signature
6
8
 
7
9
  import voluptuous as vol
8
10
 
@@ -20,6 +22,15 @@ UNSUPPORTED = object()
20
22
  def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
21
23
  """Convert a voluptuous schema to a OpenAPI Schema object."""
22
24
  # pylint: disable=too-many-return-statements,too-many-branches
25
+
26
+ def ensure_default(value: dict[str:Any]):
27
+ """Make sure that type is set."""
28
+ if all(
29
+ x not in value for x in ("type", "enum", "anyOf", "oneOf", "allOf", "not")
30
+ ):
31
+ value["type"] = "string" # Type not determined, using default
32
+ return value
33
+
23
34
  additional_properties = None
24
35
  if isinstance(schema, vol.Schema):
25
36
  if schema.extra == vol.ALLOW_EXTRA:
@@ -31,6 +42,13 @@ def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
31
42
  if val is not UNSUPPORTED:
32
43
  return val
33
44
 
45
+ if isinstance(schema, vol.Object):
46
+ schema = schema.schema
47
+ if custom_serializer:
48
+ val = custom_serializer(schema)
49
+ if val is not UNSUPPORTED:
50
+ return val
51
+
34
52
  if isinstance(schema, Mapping):
35
53
  properties = {}
36
54
  required = []
@@ -67,6 +85,7 @@ def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
67
85
  if key.default is not vol.UNDEFINED:
68
86
  pval["default"] = key.default()
69
87
 
88
+ pval = ensure_default(pval)
70
89
  pkey = str(pkey)
71
90
  properties[pkey] = pval
72
91
 
@@ -94,7 +113,7 @@ def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
94
113
  val.update(v)
95
114
  if fallback:
96
115
  return {"allOf": allOf}
97
- return val
116
+ return ensure_default(val)
98
117
 
99
118
  if isinstance(schema, (vol.Clamp, vol.Range)):
100
119
  val = {}
@@ -143,18 +162,23 @@ def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
143
162
  }
144
163
 
145
164
  if isinstance(schema, vol.Any):
146
- # vol.Maybe
147
- if len(schema.validators) == 2 and schema.validators[0] is None:
148
- result = convert(schema.validators[1], custom_serializer=custom_serializer)
165
+ schema = schema.validators
166
+ if None in schema or NoneType in schema:
167
+ schema = [val for val in schema if val is not None and val is not NoneType]
168
+ nullable = True
169
+ else:
170
+ nullable = False
171
+ if len(schema) == 1:
172
+ result = convert(schema[0], custom_serializer=custom_serializer)
173
+ else:
174
+ result = {
175
+ "anyOf": [
176
+ convert(val, custom_serializer=custom_serializer) for val in schema
177
+ ]
178
+ }
179
+ if nullable:
149
180
  result["nullable"] = True
150
- return result
151
-
152
- return {
153
- "anyOf": [
154
- convert(val, custom_serializer=custom_serializer)
155
- for val in schema.validators
156
- ]
157
- }
181
+ return result
158
182
 
159
183
  if isinstance(schema, vol.Coerce):
160
184
  schema = schema.type
@@ -166,19 +190,61 @@ def convert(schema: Any, *, custom_serializer: Callable | None = None) -> dict:
166
190
  if len(schema) == 1:
167
191
  return {
168
192
  "type": "array",
169
- "items": convert(schema[0], custom_serializer=custom_serializer),
193
+ "items": ensure_default(
194
+ convert(schema[0], custom_serializer=custom_serializer)
195
+ ),
170
196
  }
171
197
  return {
172
198
  "type": "array",
173
199
  "items": [
174
- convert(s, custom_serializer=custom_serializer) for s in schema.items()
200
+ ensure_default(convert(s, custom_serializer=custom_serializer))
201
+ for s in schema.items()
175
202
  ],
176
203
  }
177
204
 
178
205
  if schema in TYPES_MAP:
179
206
  return {"type": TYPES_MAP[schema]}
180
207
 
181
- if isinstance(schema, type) and issubclass(schema, Enum):
182
- return {"enum": [item.value for item in schema]}
208
+ if schema is list or schema is set:
209
+ return {"type": "array", "items": ensure_default({})}
210
+
211
+ if schema is dict:
212
+ return {"type": "object", "additionalProperties": True}
213
+
214
+ if isinstance(schema, type):
215
+ if issubclass(schema, Enum):
216
+ return {"enum": [item.value for item in schema]}
217
+ elif schema is NoneType:
218
+ return {"enum": [None]}
219
+
220
+ if callable(schema):
221
+ schema = get_type_hints(schema).get(
222
+ list(signature(schema).parameters.keys())[0], Any
223
+ )
224
+ if schema is Any or isinstance(schema, TypeVar):
225
+ return {}
226
+ if isinstance(schema, UnionType) or get_origin(schema) is Union:
227
+ schema = [t for t in get_args(schema) if not isinstance(t, TypeVar)]
228
+ if len(schema) > 1:
229
+ schema = vol.Any(*schema)
230
+ elif len(schema) == 1 and schema[0] is not NoneType:
231
+ schema = schema[0]
232
+ else:
233
+ return {}
234
+ if get_origin(schema) is dict:
235
+ schema = get_args(schema)[1]
236
+ if schema is Any or isinstance(schema, TypeVar):
237
+ schema = dict
238
+ else:
239
+ return {
240
+ "type": "object",
241
+ "additionalProperties": convert(
242
+ schema, custom_serializer=custom_serializer
243
+ ),
244
+ }
245
+ if get_origin(schema) is list or get_origin(schema) is set:
246
+ schema = [get_args(schema)[0]]
247
+
248
+ return convert(schema, custom_serializer=custom_serializer)
183
249
 
184
250
  raise ValueError("Unable to convert schema: {}".format(schema))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: voluptuous-openapi
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Convert voluptuous schemas to OpenAPI Schema object
5
5
  Home-page: http://github.com/Shulyaka/voluptuous-openapi
6
6
  Author: Denis Shulyaka