voluptuous-openapi 0.4.0__tar.gz → 0.4.1__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.
- {voluptuous_openapi-0.4.0/voluptuous_openapi.egg-info → voluptuous_openapi-0.4.1}/PKG-INFO +1 -1
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/pyproject.toml +1 -1
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/tests/test_lib.py +130 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi/__init__.py +43 -5
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1/voluptuous_openapi.egg-info}/PKG-INFO +1 -1
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/LICENSE +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/README.md +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/setup.cfg +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/tests/test_validation.py +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/SOURCES.txt +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/dependency_links.txt +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/requires.txt +0 -0
- {voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/top_level.txt +0 -0
|
@@ -1315,3 +1315,133 @@ def test_anonymized_ghp_home_automation_invalid() -> None:
|
|
|
1315
1315
|
}
|
|
1316
1316
|
with pytest.raises(vol.Invalid):
|
|
1317
1317
|
validator(invalid_automation)
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def test_convert_schema_with_reference() -> None:
|
|
1321
|
+
"""Test that converting a voluptuous schema containing a reference back to OpenAPI works."""
|
|
1322
|
+
schema = {
|
|
1323
|
+
"$defs": {
|
|
1324
|
+
"PositiveInteger": {
|
|
1325
|
+
"type": "integer",
|
|
1326
|
+
"minimum": 0,
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
"type": "object",
|
|
1330
|
+
"properties": {
|
|
1331
|
+
"value": {"$ref": "#/$defs/PositiveInteger"},
|
|
1332
|
+
},
|
|
1333
|
+
}
|
|
1334
|
+
vol_schema = convert_to_voluptuous(schema)
|
|
1335
|
+
|
|
1336
|
+
# Verify that the returned vol_schema validates correctly
|
|
1337
|
+
assert vol_schema({"value": 42}) == {"value": 42}
|
|
1338
|
+
with pytest.raises(vol.Invalid):
|
|
1339
|
+
vol_schema({"value": -5})
|
|
1340
|
+
|
|
1341
|
+
# Verify that convert() successfully serializes the voluptuous schema
|
|
1342
|
+
# and denormalizes the reference.
|
|
1343
|
+
res = convert(vol_schema)
|
|
1344
|
+
assert res == {
|
|
1345
|
+
"type": "object",
|
|
1346
|
+
"properties": {
|
|
1347
|
+
"value": {"type": "integer", "minimum": 0},
|
|
1348
|
+
},
|
|
1349
|
+
"required": [],
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
def test_convert_schema_with_nested_reference() -> None:
|
|
1354
|
+
"""Test that converting a voluptuous schema containing a nested reference back to OpenAPI works."""
|
|
1355
|
+
schema = {
|
|
1356
|
+
"$defs": {
|
|
1357
|
+
"PositiveInteger": {
|
|
1358
|
+
"type": "integer",
|
|
1359
|
+
"minimum": 0,
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
"type": "object",
|
|
1363
|
+
"properties": {
|
|
1364
|
+
"nested": {
|
|
1365
|
+
"type": "object",
|
|
1366
|
+
"properties": {
|
|
1367
|
+
"value": {"$ref": "#/$defs/PositiveInteger"},
|
|
1368
|
+
},
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
}
|
|
1372
|
+
vol_schema = convert_to_voluptuous(schema)
|
|
1373
|
+
|
|
1374
|
+
# Verify that the returned vol_schema validates correctly
|
|
1375
|
+
assert vol_schema({"nested": {"value": 42}}) == {"nested": {"value": 42}}
|
|
1376
|
+
with pytest.raises(vol.Invalid):
|
|
1377
|
+
vol_schema({"nested": {"value": -5}})
|
|
1378
|
+
|
|
1379
|
+
# Verify that convert() successfully serializes the voluptuous schema and denormalizes the nested reference.
|
|
1380
|
+
res = convert(vol_schema)
|
|
1381
|
+
assert res == {
|
|
1382
|
+
"type": "object",
|
|
1383
|
+
"properties": {
|
|
1384
|
+
"nested": {
|
|
1385
|
+
"type": "object",
|
|
1386
|
+
"properties": {
|
|
1387
|
+
"value": {"type": "integer", "minimum": 0},
|
|
1388
|
+
},
|
|
1389
|
+
"required": [],
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
"required": [],
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def test_convert_recursive_schema() -> None:
|
|
1397
|
+
"""Test that converting a voluptuous schema containing a recursive reference back to OpenAPI works."""
|
|
1398
|
+
schema = {
|
|
1399
|
+
"$defs": {
|
|
1400
|
+
"Node": {
|
|
1401
|
+
"type": "object",
|
|
1402
|
+
"properties": {
|
|
1403
|
+
"value": {"type": "string"},
|
|
1404
|
+
"child": {"$ref": "#/$defs/Node"},
|
|
1405
|
+
},
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
"$ref": "#/$defs/Node",
|
|
1409
|
+
}
|
|
1410
|
+
vol_schema = convert_to_voluptuous(schema)
|
|
1411
|
+
|
|
1412
|
+
# Verify that the returned vol_schema validates correctly
|
|
1413
|
+
valid_data = {"value": "root", "child": {"value": "child"}}
|
|
1414
|
+
assert vol_schema(valid_data) == valid_data
|
|
1415
|
+
|
|
1416
|
+
# Verify that convert() successfully serializes the voluptuous schema,
|
|
1417
|
+
# denormalizes the reference, and breaks the cycle at "child".
|
|
1418
|
+
res = convert(vol_schema)
|
|
1419
|
+
assert res == {
|
|
1420
|
+
"type": "object",
|
|
1421
|
+
"properties": {
|
|
1422
|
+
"value": {"type": "string"},
|
|
1423
|
+
"child": {"type": "string"},
|
|
1424
|
+
},
|
|
1425
|
+
"required": [],
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def test_convert_custom_callable_class_instance() -> None:
|
|
1430
|
+
"""Test that convert() handles custom callable validator instances."""
|
|
1431
|
+
|
|
1432
|
+
class CustomValidator:
|
|
1433
|
+
def __call__(self, value: int) -> int:
|
|
1434
|
+
if value < 0:
|
|
1435
|
+
raise vol.Invalid("Must be positive")
|
|
1436
|
+
return value
|
|
1437
|
+
|
|
1438
|
+
vol_schema = vol.Schema(CustomValidator())
|
|
1439
|
+
|
|
1440
|
+
# Verify native validation works
|
|
1441
|
+
assert vol_schema(42) == 42
|
|
1442
|
+
with pytest.raises(vol.Invalid):
|
|
1443
|
+
vol_schema(-5)
|
|
1444
|
+
|
|
1445
|
+
# Verify serialization extracts the type hint from __call__ parameter
|
|
1446
|
+
res = convert(vol_schema)
|
|
1447
|
+
assert res == {"type": "integer"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Module to convert voluptuous schemas to dictionaries."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Mapping, Sequence
|
|
4
|
-
from inspect import signature
|
|
4
|
+
from inspect import isroutine, signature
|
|
5
5
|
from enum import Enum, StrEnum
|
|
6
6
|
import itertools
|
|
7
7
|
import re
|
|
@@ -47,6 +47,7 @@ def convert(
|
|
|
47
47
|
*,
|
|
48
48
|
custom_serializer: Callable | None = None,
|
|
49
49
|
openapi_version: OpenApiVersion = OpenApiVersion.V3,
|
|
50
|
+
_seen_refs: set[str] | None = None,
|
|
50
51
|
) -> dict:
|
|
51
52
|
"""Convert a voluptuous schema to a OpenAPI Schema object."""
|
|
52
53
|
# pylint: disable=too-many-return-statements,too-many-branches
|
|
@@ -54,9 +55,28 @@ def convert(
|
|
|
54
55
|
def convert_with_args(schema: Any) -> dict:
|
|
55
56
|
"""Convert schema for recusing and propagating arguments."""
|
|
56
57
|
return convert(
|
|
57
|
-
schema,
|
|
58
|
+
schema,
|
|
59
|
+
custom_serializer=custom_serializer,
|
|
60
|
+
openapi_version=openapi_version,
|
|
61
|
+
_seen_refs=_seen_refs,
|
|
58
62
|
)
|
|
59
63
|
|
|
64
|
+
if isinstance(schema, LazySchema):
|
|
65
|
+
# Recursively resolve and denormalize references. We track visited references
|
|
66
|
+
# in _seen_refs to detect cycle/recursion loops. If a cycle is detected, we
|
|
67
|
+
# break it by returning {} (representing Any).
|
|
68
|
+
if _seen_refs is None:
|
|
69
|
+
_seen_refs = set()
|
|
70
|
+
if schema.ref in _seen_refs:
|
|
71
|
+
return {}
|
|
72
|
+
_seen_refs.add(schema.ref)
|
|
73
|
+
try:
|
|
74
|
+
target_schema = resolve_ref(schema.ref, schema.root_schema)
|
|
75
|
+
vol_target = convert_to_voluptuous(target_schema, schema.root_schema)
|
|
76
|
+
return convert_with_args(vol_target)
|
|
77
|
+
finally:
|
|
78
|
+
_seen_refs.remove(schema.ref)
|
|
79
|
+
|
|
60
80
|
def ensure_default(value: dict[str:Any]):
|
|
61
81
|
"""Make sure that type is set."""
|
|
62
82
|
if all(x not in value for x in ("type", "anyOf", "oneOf", "allOf", "not")):
|
|
@@ -433,9 +453,16 @@ def convert(
|
|
|
433
453
|
return {"type": "object", "additionalProperties": True}
|
|
434
454
|
|
|
435
455
|
if callable(schema):
|
|
436
|
-
schema
|
|
437
|
-
|
|
438
|
-
|
|
456
|
+
if isinstance(schema, type) or isroutine(schema):
|
|
457
|
+
hints = get_type_hints(schema)
|
|
458
|
+
else:
|
|
459
|
+
# For custom callable class instances (such as LazySchema), we inspect
|
|
460
|
+
# the __call__ method instead. We cannot fully serialize arbitrary Python
|
|
461
|
+
# callables back to OpenAPI, but we can try to extract the type annotation
|
|
462
|
+
# of their first parameter to preserve type information in the generated schema.
|
|
463
|
+
hints = get_type_hints(schema.__call__)
|
|
464
|
+
params = list(signature(schema).parameters.keys())
|
|
465
|
+
schema = hints.get(params[0], Any) if params else Any
|
|
439
466
|
if schema is Any or isinstance(schema, TypeVar):
|
|
440
467
|
return {}
|
|
441
468
|
if isinstance(schema, UnionType) or get_origin(schema) is Union:
|
|
@@ -499,6 +526,14 @@ class LazySchema:
|
|
|
499
526
|
By returning a LazySchema instance during compilation, we avoid infinite
|
|
500
527
|
recursion/loops for circular or self-referential schemas. The schema is
|
|
501
528
|
resolved and compiled only on its first invocation, and cached for future runs.
|
|
529
|
+
|
|
530
|
+
Serialization Round-Trip Caveats:
|
|
531
|
+
When converting a voluptuous schema containing a LazySchema back to an OpenAPI
|
|
532
|
+
schema:
|
|
533
|
+
1. Non-recursive references are fully denormalized (resolved and expanded).
|
|
534
|
+
2. Recursive/circular references are broken at the recursion boundary and return
|
|
535
|
+
an empty schema {} (representing Any, which is defaulted to a string type by
|
|
536
|
+
the library's ensure_default helper) to prevent infinite recursion/loops.
|
|
502
537
|
"""
|
|
503
538
|
|
|
504
539
|
def __init__(self, ref: str, root_schema: dict):
|
|
@@ -525,6 +560,9 @@ class LazySchema:
|
|
|
525
560
|
return False
|
|
526
561
|
return self.ref == other.ref and self.root_schema == other.root_schema
|
|
527
562
|
|
|
563
|
+
def __hash__(self) -> int:
|
|
564
|
+
return hash(self.ref)
|
|
565
|
+
|
|
528
566
|
def __repr__(self) -> str:
|
|
529
567
|
return f"LazySchema({self.ref!r})"
|
|
530
568
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/requires.txt
RENAMED
|
File without changes
|
{voluptuous_openapi-0.4.0 → voluptuous_openapi-0.4.1}/voluptuous_openapi.egg-info/top_level.txt
RENAMED
|
File without changes
|