Feature/explicit exception type #47
37
docs/source/jambo.exceptions.rst
Normal file
37
docs/source/jambo.exceptions.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
jambo.exceptions package
|
||||
========================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
jambo.exceptions.internal\_assertion\_exception module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.internal_assertion_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.exceptions.invalid\_schema\_exception module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.invalid_schema_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.exceptions.unsupported\_schema\_exception module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.unsupported_schema_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: jambo.exceptions
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
@@ -36,6 +36,22 @@ jambo.parser.boolean\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.const\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.const_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.enum\_type\_parser module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.enum_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.float\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
@@ -52,6 +68,14 @@ jambo.parser.int\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.null\_type\_parser module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.null_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.object\_type\_parser module
|
||||
----------------------------------------
|
||||
|
||||
@@ -60,6 +84,14 @@ jambo.parser.object\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.oneof\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.oneof_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.ref\_type\_parser module
|
||||
-------------------------------------
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ Subpackages
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
jambo.exceptions
|
||||
jambo.parser
|
||||
jambo.types
|
||||
|
||||
|
||||
10
jambo/exceptions/__init__.py
Normal file
10
jambo/exceptions/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .internal_assertion_exception import InternalAssertionException
|
||||
from .invalid_schema_exception import InvalidSchemaException
|
||||
from .unsupported_schema_exception import UnsupportedSchemaException
|
||||
|
||||
|
||||
__all__ = [
|
||||
"InternalAssertionException",
|
||||
"InvalidSchemaException",
|
||||
"UnsupportedSchemaException",
|
||||
]
|
||||
16
jambo/exceptions/internal_assertion_exception.py
Normal file
16
jambo/exceptions/internal_assertion_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
class InternalAssertionException(AssertionError):
|
||||
"""Exception raised for internal assertions."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Internal Assertion Failed: ")
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"Internal Assertion Failed: {super().__str__()}\n"
|
||||
"This is likely a bug in Jambo. Please report it at"
|
||||
)
|
||||
27
jambo/exceptions/invalid_schema_exception.py
Normal file
27
jambo/exceptions/invalid_schema_exception.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing_extensions import Optional
|
||||
|
||||
|
||||
class InvalidSchemaException(ValueError):
|
||||
"""Exception raised for invalid JSON schemas."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
invalid_field: Optional[str] = None,
|
||||
cause: Optional[BaseException] = None,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Invalid JSON Schema: ")
|
||||
self.invalid_field = invalid_field
|
||||
self.cause = cause
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
base_msg = f"Invalid JSON Schema: {super().__str__()}"
|
||||
if self.invalid_field:
|
||||
return f"{base_msg} (invalid field: {self.invalid_field})"
|
||||
if self.cause:
|
||||
return (
|
||||
f"{base_msg} (caused by {self.cause.__class__.__name__}: {self.cause})"
|
||||
)
|
||||
return base_msg
|
||||
23
jambo/exceptions/unsupported_schema_exception.py
Normal file
23
jambo/exceptions/unsupported_schema_exception.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing_extensions import Optional
|
||||
|
||||
|
||||
class UnsupportedSchemaException(ValueError):
|
||||
"""Exception raised for unsupported JSON schemas."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
unsupported_field: Optional[str] = None,
|
||||
cause: Optional[BaseException] = None,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Unsupported JSON Schema: ")
|
||||
self.unsupported_field = unsupported_field
|
||||
self.cause = cause
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
base_msg = f"Unsupported JSON Schema: {super().__str__()}"
|
||||
if self.unsupported_field:
|
||||
return f"{base_msg} (unsupported field: {self.unsupported_field})"
|
||||
return base_msg
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.types.type_parser_options import JSONSchema, TypeParserOptions
|
||||
|
||||
from pydantic import Field, TypeAdapter
|
||||
@@ -46,8 +47,8 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
)
|
||||
|
||||
if not self._validate_default(parsed_type, parsed_properties):
|
||||
raise ValueError(
|
||||
f"Default value {properties.get('default')} is not valid for type {parsed_type.__name__}"
|
||||
raise InvalidSchemaException(
|
||||
"Default value is not valid", invalid_field=name
|
||||
)
|
||||
|
||||
return parsed_type, parsed_properties
|
||||
@@ -79,7 +80,9 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
if schema_value is None or schema_value == properties[schema_type]: # type: ignore
|
||||
return subcls
|
||||
|
||||
raise ValueError("Unknown type")
|
||||
raise InvalidSchemaException(
|
||||
"No suitable type parser found", invalid_field=str(properties)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_schema_type(cls) -> tuple[str, str | None]:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
@@ -33,13 +34,18 @@ class AllOfTypeParser(GenericTypeParser):
|
||||
sub_properties: list[JSONSchema],
|
||||
) -> type[GenericTypeParser]:
|
||||
if not sub_properties:
|
||||
raise ValueError("Invalid JSON Schema: 'allOf' is empty.")
|
||||
raise InvalidSchemaException(
|
||||
"'allOf' must contain at least one schema", invalid_field="allOf"
|
||||
)
|
||||
|
||||
parsers: set[type[GenericTypeParser]] = set(
|
||||
GenericTypeParser._get_impl(sub_property) for sub_property in sub_properties
|
||||
)
|
||||
if len(parsers) != 1:
|
||||
raise ValueError("Invalid JSON Schema: allOf types do not match.")
|
||||
raise InvalidSchemaException(
|
||||
"All sub-schemas in 'allOf' must resolve to the same type",
|
||||
invalid_field="allOf",
|
||||
)
|
||||
|
||||
return parsers.pop()
|
||||
|
||||
@@ -68,8 +74,8 @@ class AllOfTypeParser(GenericTypeParser):
|
||||
|
||||
if prop_name == "default":
|
||||
if old_value != new_value:
|
||||
raise ValueError(
|
||||
f"Invalid JSON Schema: conflicting defaults for '{prop_name}'"
|
||||
raise InvalidSchemaException(
|
||||
f"Conflicting defaults for '{prop_name}'", invalid_field=prop_name
|
||||
)
|
||||
return old_value
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -14,10 +15,15 @@ class AnyOfTypeParser(GenericTypeParser):
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "anyOf" not in properties:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||
raise InvalidSchemaException(
|
||||
f"AnyOf type {name} must have 'anyOf' property defined.",
|
||||
invalid_field="anyOf",
|
||||
)
|
||||
|
||||
if not isinstance(properties["anyOf"], list):
|
||||
raise ValueError(f"Invalid JSON Schema: {properties['anyOf']}")
|
||||
raise InvalidSchemaException(
|
||||
"AnyOf must be a list of types.", invalid_field="anyOf"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -26,8 +27,15 @@ class ArrayTypeParser(GenericTypeParser):
|
||||
):
|
||||
item_properties = kwargs.copy()
|
||||
item_properties["required"] = True
|
||||
|
||||
if (items := properties.get("items")) is None:
|
||||
raise InvalidSchemaException(
|
||||
f"Array type {name} must have 'items' property defined.",
|
||||
invalid_field="items",
|
||||
)
|
||||
|
||||
_item_type, _item_args = GenericTypeParser.type_from_properties(
|
||||
name, properties["items"], **item_properties
|
||||
name, items, **item_properties
|
||||
)
|
||||
|
||||
wrapper_type = set if properties.get("uniqueItems", False) else list
|
||||
@@ -47,8 +55,9 @@ class ArrayTypeParser(GenericTypeParser):
|
||||
return lambda: None
|
||||
|
||||
if not isinstance(default_list, Iterable):
|
||||
raise ValueError(
|
||||
f"Default value for array must be an iterable, got {type(default_list)}"
|
||||
raise InvalidSchemaException(
|
||||
f"Default value for array must be an iterable, got {type(default_list)}",
|
||||
invalid_field="default",
|
||||
)
|
||||
|
||||
return lambda: copy.deepcopy(wrapper_type(default_list))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -20,6 +21,9 @@ class BooleanTypeParser(GenericTypeParser):
|
||||
|
||||
default_value = properties.get("default")
|
||||
if default_value is not None and not isinstance(default_value, bool):
|
||||
raise ValueError(f"Default value for {name} must be a boolean.")
|
||||
raise InvalidSchemaException(
|
||||
f"Default value for {name} must be a boolean.",
|
||||
invalid_field="default",
|
||||
)
|
||||
|
||||
return bool, mapped_properties
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
@@ -18,13 +19,17 @@ class ConstTypeParser(GenericTypeParser):
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "const" not in properties:
|
||||
raise ValueError(f"Const type {name} must have 'const' property defined.")
|
||||
raise InvalidSchemaException(
|
||||
f"Const type {name} must have 'const' property defined.",
|
||||
invalid_field="const",
|
||||
)
|
||||
|
||||
const_value = properties["const"]
|
||||
|
||||
if not isinstance(const_value, JSONSchemaNativeTypes):
|
||||
raise ValueError(
|
||||
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}."
|
||||
raise InvalidSchemaException(
|
||||
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}.",
|
||||
invalid_field="const",
|
||||
)
|
||||
|
||||
const_type = self._build_const_type(const_value)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||
from jambo.types.type_parser_options import JSONSchema, TypeParserOptions
|
||||
@@ -14,16 +15,23 @@ class EnumTypeParser(GenericTypeParser):
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "enum" not in properties:
|
||||
raise ValueError(f"Enum type {name} must have 'enum' property defined.")
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' property defined.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
enum_values = properties["enum"]
|
||||
|
||||
if not isinstance(enum_values, list):
|
||||
raise ValueError(f"Enum type {name} must have 'enum' as a list of values.")
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' as a list of values.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values):
|
||||
raise ValueError(
|
||||
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}."
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
# Create a new Enum type dynamically
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -17,10 +18,14 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "oneOf" not in properties:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid JSON Schema: {properties}", invalid_field="oneOf"
|
||||
)
|
||||
|
||||
if not isinstance(properties["oneOf"], list) or len(properties["oneOf"]) == 0:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}")
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid JSON Schema: {properties['oneOf']}", invalid_field="oneOf"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
@@ -58,7 +63,9 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
Build a type with a discriminator.
|
||||
"""
|
||||
if not isinstance(discriminator_prop, dict):
|
||||
raise ValueError("Discriminator must be a dictionary")
|
||||
raise InvalidSchemaException(
|
||||
"Discriminator must be a dictionary", invalid_field="discriminator"
|
||||
)
|
||||
|
||||
for field in subfield_types:
|
||||
field_type, field_info = get_args(field)
|
||||
@@ -66,13 +73,17 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
if issubclass(field_type, BaseModel):
|
||||
continue
|
||||
|
||||
raise ValueError(
|
||||
"When using a discriminator, all subfield types must be of type 'object'."
|
||||
raise InvalidSchemaException(
|
||||
"When using a discriminator, all subfield types must be of type 'object'.",
|
||||
invalid_field="discriminator",
|
||||
)
|
||||
|
||||
property_name = discriminator_prop.get("propertyName")
|
||||
if property_name is None or not isinstance(property_name, str):
|
||||
raise ValueError("Discriminator must have a 'propertyName' key")
|
||||
raise InvalidSchemaException(
|
||||
"Discriminator must have a 'propertyName' key",
|
||||
invalid_field="propertyName",
|
||||
)
|
||||
|
||||
return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)]
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InternalAssertionException, InvalidSchemaException
|
||||
from jambo.parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
@@ -17,18 +18,19 @@ class RefTypeParser(GenericTypeParser):
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[RefType, dict]:
|
||||
if "$ref" not in properties:
|
||||
raise ValueError(f"RefTypeParser: Missing $ref in properties for {name}")
|
||||
raise InvalidSchemaException(
|
||||
f"Missing $ref in properties for {name}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
context = kwargs.get("context")
|
||||
if context is None:
|
||||
raise RuntimeError(
|
||||
f"RefTypeParser: Missing `content` in properties for {name}"
|
||||
if kwargs.get("context") is None:
|
||||
raise InternalAssertionException(
|
||||
"`context` must be provided in kwargs for RefTypeParser"
|
||||
)
|
||||
|
||||
ref_cache = kwargs.get("ref_cache")
|
||||
if ref_cache is None:
|
||||
raise RuntimeError(
|
||||
f"RefTypeParser: Missing `ref_cache` in properties for {name}"
|
||||
raise InternalAssertionException(
|
||||
"`ref_cache` must be provided in kwargs for RefTypeParser"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
@@ -63,8 +65,8 @@ class RefTypeParser(GenericTypeParser):
|
||||
ref_name, ref_property, **kwargs
|
||||
)
|
||||
case _:
|
||||
raise ValueError(
|
||||
f"RefTypeParser: Unsupported $ref {ref_property['$ref']}"
|
||||
raise InvalidSchemaException(
|
||||
f"Unsupported $ref {ref_property['$ref']}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
return mapped_type
|
||||
@@ -93,8 +95,9 @@ class RefTypeParser(GenericTypeParser):
|
||||
if properties.get("$ref") == "#":
|
||||
ref_name = kwargs["context"].get("title")
|
||||
if ref_name is None:
|
||||
raise ValueError(
|
||||
"RefTypeParser: Missing title in properties for $ref of Root Reference"
|
||||
raise InvalidSchemaException(
|
||||
"Missing title in properties for $ref of Root Reference",
|
||||
invalid_field="title",
|
||||
)
|
||||
return "forward_ref", ref_name, {}
|
||||
|
||||
@@ -104,8 +107,9 @@ class RefTypeParser(GenericTypeParser):
|
||||
)
|
||||
return "def_ref", target_name, target_property
|
||||
|
||||
raise ValueError(
|
||||
"RefTypeParser: Only Root and $defs references are supported at the moment"
|
||||
raise InvalidSchemaException(
|
||||
"Only Root and $defs references are supported at the moment",
|
||||
invalid_field="$ref",
|
||||
)
|
||||
|
||||
def _extract_target_ref(
|
||||
@@ -115,14 +119,16 @@ class RefTypeParser(GenericTypeParser):
|
||||
target_property = kwargs["context"]
|
||||
for prop_name in properties["$ref"].split("/")[1:]:
|
||||
if prop_name not in target_property:
|
||||
raise ValueError(
|
||||
f"RefTypeParser: Missing {prop_name} in"
|
||||
" properties for $ref {properties['$ref']}"
|
||||
raise InvalidSchemaException(
|
||||
f"Missing {prop_name} in properties for $ref {properties['$ref']}",
|
||||
invalid_field=prop_name,
|
||||
)
|
||||
target_name = prop_name
|
||||
target_property = target_property[prop_name] # type: ignore
|
||||
|
||||
if not isinstance(target_name, str) or target_property is None:
|
||||
raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}")
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid $ref {properties['$ref']}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
return target_name, target_property
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -22,19 +23,19 @@ class StringTypeParser(GenericTypeParser):
|
||||
}
|
||||
|
||||
format_type_mapping = {
|
||||
# 7.3.1. Dates, Times, and Duration
|
||||
# [7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.1). Dates, Times, and Duration
|
||||
"date": date,
|
||||
"time": time,
|
||||
"date-time": datetime,
|
||||
"duration": timedelta,
|
||||
# 7.3.2. Email Addresses
|
||||
# [7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.2). Email Addresses
|
||||
"email": EmailStr,
|
||||
# 7.3.3. Hostnames
|
||||
# [7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.3). Hostnames
|
||||
"hostname": str,
|
||||
# 7.3.4. IP Addresses
|
||||
# [7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.4). IP Addresses
|
||||
"ipv4": IPv4Address,
|
||||
"ipv6": IPv6Address,
|
||||
# 7.3.5. Resource Identifiers
|
||||
# [7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.5). Resource Identifiers
|
||||
"uri": AnyUrl,
|
||||
# "iri" # Not supported by pydantic and currently not supported by jambo
|
||||
"uuid": UUID,
|
||||
@@ -54,7 +55,9 @@ class StringTypeParser(GenericTypeParser):
|
||||
return str, mapped_properties
|
||||
|
||||
if format_type not in self.format_type_mapping:
|
||||
raise ValueError(f"Unsupported string format: {format_type}")
|
||||
raise InvalidSchemaException(
|
||||
f"Unsupported string format: {format_type}", invalid_field="format"
|
||||
)
|
||||
|
||||
mapped_type = self.format_type_mapping[format_type]
|
||||
if format_type in self.format_pattern_mapping:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException
|
||||
from jambo.parser import ObjectTypeParser, RefTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types import JSONSchema
|
||||
|
||||
from jsonschema.exceptions import SchemaError
|
||||
from jsonschema.validators import validator_for
|
||||
@@ -26,11 +27,15 @@ class SchemaConverter:
|
||||
try:
|
||||
validator = validator_for(schema)
|
||||
validator.check_schema(schema) # type: ignore
|
||||
except SchemaError as e:
|
||||
raise ValueError(f"Invalid JSON Schema: {e}")
|
||||
except SchemaError as err:
|
||||
raise InvalidSchemaException(
|
||||
"Validation of JSON Schema failed.", cause=err
|
||||
) from err
|
||||
|
||||
if "title" not in schema:
|
||||
raise ValueError("JSON Schema must have a title.")
|
||||
raise InvalidSchemaException(
|
||||
"Schema must have a title.", invalid_field="title"
|
||||
)
|
||||
|
||||
schema_type = SchemaConverter._get_schema_type(schema)
|
||||
|
||||
@@ -55,7 +60,13 @@ class SchemaConverter:
|
||||
)
|
||||
return parsed_model
|
||||
case _:
|
||||
raise TypeError(f"Unsupported schema type: {schema_type}")
|
||||
unsupported_type = (
|
||||
f"type:{schema_type}" if schema_type else "missing type"
|
||||
)
|
||||
raise UnsupportedSchemaException(
|
||||
"Only object and $ref schema types are supported.",
|
||||
unsupported_field=unsupported_type,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_schema_type(schema: JSONSchema) -> str | None:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from .json_schema_type import (
|
||||
JSONSchema,
|
||||
JSONSchemaNativeTypes,
|
||||
JSONSchemaType,
|
||||
JSONType,
|
||||
)
|
||||
from .type_parser_options import TypeParserOptions
|
||||
|
||||
|
||||
__all__ = [
|
||||
"JSONSchemaType",
|
||||
"JSONSchemaNativeTypes",
|
||||
"JSONType",
|
||||
"JSONSchema",
|
||||
"TypeParserOptions",
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@ JSONSchema = TypedDict(
|
||||
"minProperties": int,
|
||||
"maxProperties": int,
|
||||
"dependencies": Dict[str, Union[List[str], "JSONSchema"]],
|
||||
"items": Union["JSONSchema", List["JSONSchema"]],
|
||||
"items": "JSONSchema",
|
||||
"prefixItems": List["JSONSchema"],
|
||||
"additionalItems": Union[bool, "JSONSchema"],
|
||||
"contains": "JSONSchema",
|
||||
|
||||
0
tests/exceptions/__init__.py
Normal file
0
tests/exceptions/__init__.py
Normal file
21
tests/exceptions/test_internal_assertion_exception.py
Normal file
21
tests/exceptions/test_internal_assertion_exception.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from jambo.exceptions.internal_assertion_exception import InternalAssertionException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestInternalAssertionException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(InternalAssertionException, AssertionError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = (
|
||||
f"Internal Assertion Failed: {message}\n"
|
||||
"This is likely a bug in Jambo. Please report it at"
|
||||
)
|
||||
|
||||
with self.assertRaises(InternalAssertionException) as ctx:
|
||||
raise InternalAssertionException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
44
tests/exceptions/test_invalid_schema_exception.py
Normal file
44
tests/exceptions/test_invalid_schema_exception.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from jambo.exceptions.invalid_schema_exception import InvalidSchemaException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestInternalAssertionException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(InvalidSchemaException, ValueError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = f"Invalid JSON Schema: {message}"
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_invalid_field(self):
|
||||
message = "This is an internal assertion error."
|
||||
invalid_field = "testField"
|
||||
|
||||
expected_message = (
|
||||
f"Invalid JSON Schema: {message} (invalid field: {invalid_field})"
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message, invalid_field=invalid_field)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_cause(self):
|
||||
message = "This is an internal assertion error."
|
||||
cause = ValueError("Underlying cause")
|
||||
|
||||
expected_message = (
|
||||
f"Invalid JSON Schema: {message} (caused by ValueError: Underlying cause)"
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message, cause=cause)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
31
tests/exceptions/test_unsupported_schema_exception.py
Normal file
31
tests/exceptions/test_unsupported_schema_exception.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from jambo.exceptions.unsupported_schema_exception import UnsupportedSchemaException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnsupportedSchemaException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(UnsupportedSchemaException, ValueError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = f"Unsupported JSON Schema: {message}"
|
||||
|
||||
with self.assertRaises(UnsupportedSchemaException) as ctx:
|
||||
raise UnsupportedSchemaException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_unsupported_field(self):
|
||||
message = "This is an internal assertion error."
|
||||
invalid_field = "testField"
|
||||
|
||||
expected_message = (
|
||||
f"Unsupported JSON Schema: {message} (unsupported field: {invalid_field})"
|
||||
)
|
||||
|
||||
with self.assertRaises(UnsupportedSchemaException) as ctx:
|
||||
raise UnsupportedSchemaException(message, unsupported_field=invalid_field)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
@@ -1,5 +1,8 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.allof_type_parser import AllOfTypeParser
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
@@ -42,13 +45,13 @@ class TestAllOfTypeParser(TestCase):
|
||||
"placeholder", properties
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John", age=101)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="", age=30)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John Invalid", age=30)
|
||||
|
||||
obj = type_parsing(name="John", age=30)
|
||||
@@ -87,10 +90,10 @@ class TestAllOfTypeParser(TestCase):
|
||||
"placeholder", properties
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(age=30)
|
||||
|
||||
obj = type_parsing(name="John", age=30)
|
||||
@@ -154,7 +157,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_all_of_invalid_type_not_present(self):
|
||||
@@ -167,7 +170,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_all_of_invalid_type_in_fields(self):
|
||||
@@ -180,7 +183,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_all_of_invalid_type_not_all_equal(self):
|
||||
@@ -196,7 +199,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_all_of_description_field(self):
|
||||
@@ -304,5 +307,5 @@ class TestAllOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.anyof_type_parser import AnyOfTypeParser
|
||||
|
||||
from typing_extensions import Annotated, Union, get_args, get_origin
|
||||
@@ -14,7 +15,7 @@ class TestAnyOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_any_of_with_invalid_properties(self):
|
||||
@@ -22,7 +23,7 @@ class TestAnyOfTypeParser(TestCase):
|
||||
"anyOf": None,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_any_of_string_or_int(self):
|
||||
@@ -95,5 +96,5 @@ class TestAnyOfTypeParser(TestCase):
|
||||
"default": 3.14,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import ArrayTypeParser
|
||||
|
||||
from typing_extensions import get_args
|
||||
@@ -18,6 +19,17 @@ class TestArrayTypeParser(TestCase):
|
||||
self.assertEqual(type_parsing.__origin__, list)
|
||||
self.assertEqual(element_type, str)
|
||||
|
||||
def test_array_parser_with_no_items(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
properties = {
|
||||
"default": ["a", "b", "c", "d"],
|
||||
"maxItems": 3,
|
||||
}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_options_unique(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
@@ -67,7 +79,7 @@ class TestArrayTypeParser(TestCase):
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_type(self):
|
||||
@@ -75,15 +87,15 @@ class TestArrayTypeParser(TestCase):
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": 000}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
parser.from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties=properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_min(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_max(self):
|
||||
@@ -95,5 +107,5 @@ class TestArrayTypeParser(TestCase):
|
||||
"maxItems": 3,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import BooleanTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -39,5 +40,5 @@ class TestBoolTypeParser(TestCase):
|
||||
"default": "invalid",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties_impl("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import ConstTypeParser
|
||||
|
||||
from typing_extensions import Annotated, Literal, get_args, get_origin
|
||||
@@ -80,7 +81,7 @@ class TestConstTypeParser(TestCase):
|
||||
expected_const_value = "United States of America"
|
||||
properties = {"notConst": expected_const_value}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties_impl("invalid_country", properties)
|
||||
|
||||
self.assertIn(
|
||||
@@ -93,7 +94,7 @@ class TestConstTypeParser(TestCase):
|
||||
|
||||
properties = {"const": {}}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties_impl("invalid_country", properties)
|
||||
|
||||
self.assertIn(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import EnumTypeParser
|
||||
|
||||
from enum import Enum
|
||||
@@ -10,7 +11,7 @@ class TestEnumTypeParser(TestCase):
|
||||
|
||||
schema = {}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"TestEnum",
|
||||
schema,
|
||||
@@ -23,7 +24,7 @@ class TestEnumTypeParser(TestCase):
|
||||
"enum": "not_a_list",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"TestEnum",
|
||||
schema,
|
||||
@@ -86,5 +87,5 @@ class TestEnumTypeParser(TestCase):
|
||||
"enum": ["value1", 42, dict()],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties_impl("TestEnum", schema)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import FloatTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -61,7 +62,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_maximum(self):
|
||||
@@ -75,7 +76,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_minimum(self):
|
||||
@@ -89,7 +90,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_exclusive_maximum(self):
|
||||
@@ -103,7 +104,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_exclusive_minimum(self):
|
||||
@@ -117,7 +118,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_multiple(self):
|
||||
@@ -131,5 +132,5 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 2.0,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import IntTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -61,7 +62,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_maximum(self):
|
||||
@@ -75,7 +76,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_minimum(self):
|
||||
@@ -89,7 +90,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_exclusive_maximum(self):
|
||||
@@ -103,7 +104,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_exclusive_minimum(self):
|
||||
@@ -117,7 +118,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_multipleOf(self):
|
||||
@@ -131,5 +132,5 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from jambo import SchemaConverter
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.oneof_type_parser import OneOfTypeParser
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestOneOfTypeParser(TestCase):
|
||||
def test_oneof_raises_on_invalid_property(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
OneOfTypeParser().from_properties_impl(
|
||||
"test_field",
|
||||
{
|
||||
@@ -17,7 +20,18 @@ class TestOneOfTypeParser(TestCase):
|
||||
ref_cache={},
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
OneOfTypeParser().from_properties_impl(
|
||||
"test_field",
|
||||
{
|
||||
"oneOf": [], # should throw because oneOf must be a list with at least one item
|
||||
},
|
||||
required=True,
|
||||
context={},
|
||||
ref_cache={},
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -71,13 +85,13 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=-5)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="invalid")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=123.45)
|
||||
|
||||
def test_oneof_with_conflicting_schemas(self):
|
||||
@@ -103,11 +117,11 @@ class TestOneOfTypeParser(TestCase):
|
||||
obj2 = Model(data=9)
|
||||
self.assertEqual(obj2.data, 9)
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
Model(data=6)
|
||||
self.assertIn("matches multiple oneOf schemas", str(cm.exception))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(data=5)
|
||||
|
||||
def test_oneof_with_objects(self):
|
||||
@@ -147,7 +161,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
obj2 = Model(contact_info={"phone": "123-456-7890"})
|
||||
self.assertEqual(obj2.contact_info.phone, "123-456-7890")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"})
|
||||
|
||||
def test_oneof_with_discriminator_basic(self):
|
||||
@@ -190,14 +204,14 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(dog.pet.type, "dog")
|
||||
self.assertEqual(dog.pet.barks, False)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(pet={"type": "cat", "barks": True})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(pet={"type": "bird", "flies": True})
|
||||
|
||||
def test_oneof_with_invalid_types(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Pet",
|
||||
@@ -301,13 +315,13 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "triangle", "base": 5, "height": 3})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "circle", "side": 5})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"radius": 5})
|
||||
|
||||
def test_oneof_missing_properties(self):
|
||||
@@ -324,7 +338,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_invalid_properties(self):
|
||||
@@ -336,7 +350,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_with_default_value(self):
|
||||
@@ -373,12 +387,12 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_discriminator_without_property_name(self):
|
||||
# Should throw because the spec determines propertyName is required for discriminator
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -409,7 +423,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
def test_oneof_discriminator_with_invalid_discriminator(self):
|
||||
# Should throw because a valid discriminator is required
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -465,8 +479,9 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(obj2.value, "very long string")
|
||||
|
||||
# Invalid: Medium string (matches BOTH schemas - violates oneOf)
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4
|
||||
|
||||
self.assertIn("matches multiple oneOf schemas", str(cm.exception))
|
||||
|
||||
def test_oneof_shapes_discriminator_from_docs(self):
|
||||
@@ -515,5 +530,5 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(rectangle.shape.height, 20)
|
||||
|
||||
# Invalid: Wrong properties for the type
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "circle", "width": 10})
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from jambo.exceptions import InternalAssertionException, InvalidSchemaException
|
||||
from jambo.parser import ObjectTypeParser, RefTypeParser
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from typing import ForwardRef
|
||||
from unittest import TestCase
|
||||
|
||||
@@ -16,7 +19,7 @@ class TestRefTypeParser(TestCase):
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -40,7 +43,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
with self.assertRaises(InternalAssertionException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -63,7 +66,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
with self.assertRaises(InternalAssertionException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -77,7 +80,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$ref": "https://example.com/schemas/person.json",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -110,7 +113,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ObjectTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -126,7 +129,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$defs": {},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -142,7 +145,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$defs": {"person": None},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -232,7 +235,7 @@ class TestRefTypeParser(TestCase):
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ObjectTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -264,7 +267,7 @@ class TestRefTypeParser(TestCase):
|
||||
)
|
||||
|
||||
# checks if when created via FowardRef the model is validated correctly.
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(
|
||||
name="John",
|
||||
age=30,
|
||||
@@ -421,7 +424,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ref_strategy, ref_name, ref_property = RefTypeParser()._parse_from_strategy(
|
||||
"invalid_strategy",
|
||||
"person",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import StringTypeParser
|
||||
|
||||
from pydantic import AnyUrl, EmailStr
|
||||
@@ -62,7 +63,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_default_invalid_maxlength(self):
|
||||
@@ -75,7 +76,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 1,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_default_invalid_minlength(self):
|
||||
@@ -88,7 +89,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_email_format(self):
|
||||
@@ -183,11 +184,12 @@ class TestStringTypeParser(TestCase):
|
||||
"format": "unsupported-format",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(
|
||||
str(context.exception), "Unsupported string format: unsupported-format"
|
||||
str(context.exception),
|
||||
"Invalid JSON Schema: Unsupported string format: unsupported-format (invalid field: format)",
|
||||
)
|
||||
|
||||
def test_string_parser_with_date_format(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import StringTypeParser
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
|
||||
@@ -17,5 +18,5 @@ class TestGenericTypeParser(TestCase):
|
||||
StringTypeParser.json_schema_type = "type:string"
|
||||
|
||||
def test_get_impl_invalid_type(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
GenericTypeParser._get_impl({"type": "invalid_type"})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from jambo import SchemaConverter
|
||||
from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException
|
||||
|
||||
from pydantic import AnyUrl, BaseModel
|
||||
from pydantic import AnyUrl, BaseModel, ValidationError
|
||||
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from unittest import TestCase
|
||||
@@ -23,7 +24,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_invalid_schema_type(self):
|
||||
@@ -37,7 +38,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_build_expects_title(self):
|
||||
@@ -50,7 +51,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_build_expects_object(self):
|
||||
@@ -60,7 +61,7 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "string",
|
||||
}
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
with self.assertRaises(UnsupportedSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_is_invalid_field(self):
|
||||
@@ -76,7 +77,7 @@ class TestSchemaConverter(TestCase):
|
||||
# 'required': ['name', 'age', 'is_active', 'friends', 'address'],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
SchemaConverter.build(schema)
|
||||
self.assertTrue("Unknown type" in str(context.exception))
|
||||
|
||||
@@ -117,16 +118,16 @@ class TestSchemaConverter(TestCase):
|
||||
|
||||
self.assertEqual(model(name="John", age=30).name, "John")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name=123, age=30, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="John Invalid", age=45, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="", age=45, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="John", age=45, email="hideyoshi.com")
|
||||
|
||||
def test_validation_integer(self):
|
||||
@@ -148,10 +149,10 @@ class TestSchemaConverter(TestCase):
|
||||
|
||||
self.assertEqual(model(age=30).age, 30)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=-1)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=121)
|
||||
|
||||
def test_validation_float(self):
|
||||
@@ -173,10 +174,10 @@ class TestSchemaConverter(TestCase):
|
||||
|
||||
self.assertEqual(model(age=30).age, 30.0)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=-1.0)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=121.0)
|
||||
|
||||
def test_validation_boolean(self):
|
||||
@@ -219,10 +220,10 @@ class TestSchemaConverter(TestCase):
|
||||
model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(friends=[])
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(friends=["John", "Jane", "Invalid"])
|
||||
|
||||
def test_validation_list_with_missing_items(self):
|
||||
@@ -262,7 +263,7 @@ class TestSchemaConverter(TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model()
|
||||
|
||||
def test_validation_object(self):
|
||||
@@ -290,7 +291,7 @@ class TestSchemaConverter(TestCase):
|
||||
self.assertEqual(obj.address.street, "123 Main St")
|
||||
self.assertEqual(obj.address.city, "Springfield")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model()
|
||||
|
||||
def test_default_for_string(self):
|
||||
@@ -329,7 +330,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema_max_length)
|
||||
|
||||
def test_default_for_list(self):
|
||||
@@ -421,10 +422,10 @@ class TestSchemaConverter(TestCase):
|
||||
|
||||
self.assertEqual(obj.name, "J")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="John Invalid")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="")
|
||||
|
||||
def test_any_of(self):
|
||||
@@ -450,13 +451,13 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model(id="12345678901")
|
||||
self.assertEqual(obj.id, "12345678901")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="12345678901234567890")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=11)
|
||||
|
||||
def test_string_format_email(self):
|
||||
@@ -465,9 +466,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"email": {"type": "string", "format": "email"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(model(email="test@example.com").email, "test@example.com")
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(email="invalid-email")
|
||||
|
||||
def test_string_format_uri(self):
|
||||
@@ -476,11 +479,13 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"website": {"type": "string", "format": "uri"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(
|
||||
model(website="https://example.com").website, AnyUrl("https://example.com")
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(website="invalid-uri")
|
||||
|
||||
def test_string_format_ipv4(self):
|
||||
@@ -489,9 +494,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"ip": {"type": "string", "format": "ipv4"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1"))
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(ip="256.256.256.256")
|
||||
|
||||
def test_string_format_ipv6(self):
|
||||
@@ -500,12 +507,14 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"ip": {"type": "string", "format": "ipv6"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(
|
||||
model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip,
|
||||
IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(ip="invalid-ipv6")
|
||||
|
||||
def test_string_format_uuid(self):
|
||||
@@ -514,6 +523,7 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"id": {"type": "string", "format": "uuid"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -521,7 +531,7 @@ class TestSchemaConverter(TestCase):
|
||||
UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(id="invalid-uuid")
|
||||
|
||||
def test_string_format_hostname(self):
|
||||
@@ -530,9 +540,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"hostname": {"type": "string", "format": "hostname"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(model(hostname="example.com").hostname, "example.com")
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(hostname="invalid..hostname")
|
||||
|
||||
def test_string_format_datetime(self):
|
||||
@@ -541,12 +553,14 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"timestamp": {"type": "string", "format": "date-time"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(
|
||||
model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(),
|
||||
"2024-01-01T12:00:00+00:00",
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(timestamp="invalid-datetime")
|
||||
|
||||
def test_string_format_time(self):
|
||||
@@ -555,11 +569,13 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"time": {"type": "string", "format": "time"}},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
self.assertEqual(
|
||||
model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00"
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(time="25:00:00")
|
||||
|
||||
def test_string_format_unsupported(self):
|
||||
@@ -568,7 +584,8 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"field": {"type": "string", "format": "unsupported"}},
|
||||
}
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_ref_with_root_ref(self):
|
||||
@@ -726,10 +743,10 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model()
|
||||
self.assertEqual(obj.name, "United States of America")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
obj.name = "Canada"
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="Canada")
|
||||
|
||||
def test_const_type_parser_with_non_hashable_value(self):
|
||||
@@ -749,10 +766,10 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model()
|
||||
self.assertEqual(obj.name, ["Brazil"])
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
obj.name = ["Argentina"]
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name=["Argentina"])
|
||||
|
||||
def test_null_type_parser(self):
|
||||
@@ -772,5 +789,5 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model(a_thing=None)
|
||||
self.assertIsNone(obj.a_thing)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(a_thing="not none")
|
||||
|
||||
Reference in New Issue
Block a user