Feature/adds const #30
40
docs/source/usage.const.rst
Normal file
40
docs/source/usage.const.rst
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
Const Type
|
||||||
|
=================
|
||||||
|
|
||||||
|
The const type is a special data type that allows a variable to be a single, fixed value.
|
||||||
|
It does not have the same properties as the other generic types, but it has the following specific properties:
|
||||||
|
|
||||||
|
- const: The fixed value that the variable must always hold.
|
||||||
|
- description: Description of the const field.
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from jambo import SchemaConverter
|
||||||
|
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
"title": "Country",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"const": "United States of America",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
Model = SchemaConverter.build(schema)
|
||||||
|
|
||||||
|
obj = Model()
|
||||||
|
self.assertEqual(obj.name, "United States of America")
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
obj.name = "Canada"
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Model(name="Canada")
|
||||||
@@ -45,4 +45,5 @@ For more complex schemas and types see our documentation on
|
|||||||
usage.reference
|
usage.reference
|
||||||
usage.allof
|
usage.allof
|
||||||
usage.anyof
|
usage.anyof
|
||||||
usage.enum
|
usage.enum
|
||||||
|
usage.const
|
||||||
@@ -3,6 +3,7 @@ from .allof_type_parser import AllOfTypeParser
|
|||||||
from .anyof_type_parser import AnyOfTypeParser
|
from .anyof_type_parser import AnyOfTypeParser
|
||||||
from .array_type_parser import ArrayTypeParser
|
from .array_type_parser import ArrayTypeParser
|
||||||
from .boolean_type_parser import BooleanTypeParser
|
from .boolean_type_parser import BooleanTypeParser
|
||||||
|
from .const_type_parser import ConstTypeParser
|
||||||
from .enum_type_parser import EnumTypeParser
|
from .enum_type_parser import EnumTypeParser
|
||||||
from .float_type_parser import FloatTypeParser
|
from .float_type_parser import FloatTypeParser
|
||||||
from .int_type_parser import IntTypeParser
|
from .int_type_parser import IntTypeParser
|
||||||
@@ -14,6 +15,7 @@ from .string_type_parser import StringTypeParser
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"GenericTypeParser",
|
"GenericTypeParser",
|
||||||
"EnumTypeParser",
|
"EnumTypeParser",
|
||||||
|
"ConstTypeParser",
|
||||||
"AllOfTypeParser",
|
"AllOfTypeParser",
|
||||||
"AnyOfTypeParser",
|
"AnyOfTypeParser",
|
||||||
"ArrayTypeParser",
|
"ArrayTypeParser",
|
||||||
|
|||||||
43
jambo/parser/const_type_parser.py
Normal file
43
jambo/parser/const_type_parser.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from jambo.parser._type_parser import GenericTypeParser
|
||||||
|
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||||
|
from jambo.types.type_parser_options import TypeParserOptions
|
||||||
|
|
||||||
|
from pydantic import AfterValidator
|
||||||
|
from typing_extensions import Annotated, Any, Unpack
|
||||||
|
|
||||||
|
|
||||||
|
class ConstTypeParser(GenericTypeParser):
|
||||||
|
json_schema_type = "const"
|
||||||
|
|
||||||
|
default_mappings = {
|
||||||
|
"const": "default",
|
||||||
|
"description": "description",
|
||||||
|
}
|
||||||
|
|
||||||
|
def from_properties_impl(
|
||||||
|
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||||
|
):
|
||||||
|
if "const" not in properties:
|
||||||
|
raise ValueError(f"Const type {name} must have 'const' property defined.")
|
||||||
|
|
||||||
|
const_value = properties["const"]
|
||||||
|
|
||||||
|
if not isinstance(const_value, JSONSchemaNativeTypes):
|
||||||
|
raise ValueError(
|
||||||
|
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}."
|
||||||
|
)
|
||||||
|
|
||||||
|
const_type = self._build_const_type(const_value)
|
||||||
|
parsed_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||||
|
|
||||||
|
return const_type, parsed_properties
|
||||||
|
|
||||||
|
def _build_const_type(self, const_value):
|
||||||
|
def _validate_const_value(value: Any) -> Any:
|
||||||
|
if value != const_value:
|
||||||
|
raise ValueError(
|
||||||
|
f"Value must be equal to the constant value: {const_value}"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
return Annotated[type(const_value), AfterValidator(_validate_const_value)]
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from jambo.parser._type_parser import GenericTypeParser
|
from jambo.parser._type_parser import GenericTypeParser
|
||||||
|
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||||
from jambo.types.type_parser_options import TypeParserOptions
|
from jambo.types.type_parser_options import TypeParserOptions
|
||||||
|
|
||||||
from typing_extensions import Unpack
|
from typing_extensions import Unpack
|
||||||
@@ -9,16 +10,6 @@ from enum import Enum
|
|||||||
class EnumTypeParser(GenericTypeParser):
|
class EnumTypeParser(GenericTypeParser):
|
||||||
json_schema_type = "enum"
|
json_schema_type = "enum"
|
||||||
|
|
||||||
allowed_types: tuple[type] = (
|
|
||||||
str,
|
|
||||||
int,
|
|
||||||
float,
|
|
||||||
bool,
|
|
||||||
list,
|
|
||||||
set,
|
|
||||||
type(None),
|
|
||||||
)
|
|
||||||
|
|
||||||
def from_properties_impl(
|
def from_properties_impl(
|
||||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||||
):
|
):
|
||||||
@@ -31,10 +22,10 @@ class EnumTypeParser(GenericTypeParser):
|
|||||||
raise ValueError(f"Enum type {name} must have 'enum' as a list of values.")
|
raise ValueError(f"Enum type {name} must have 'enum' as a list of values.")
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
not isinstance(value, self.allowed_types) for value in enum_values
|
not isinstance(value, JSONSchemaNativeTypes) for value in enum_values
|
||||||
):
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Enum type {name} must have 'enum' values of allowed types: {self.allowed_types}."
|
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a new Enum type dynamically
|
# Create a new Enum type dynamically
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from jambo.parser._type_parser import GenericTypeParser
|
from jambo.parser._type_parser import GenericTypeParser
|
||||||
from jambo.types.type_parser_options import TypeParserOptions
|
from jambo.types.type_parser_options import TypeParserOptions
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, create_model
|
from pydantic import BaseModel, ConfigDict, Field, create_model
|
||||||
from typing_extensions import Any, Unpack
|
from typing_extensions import Any, Unpack
|
||||||
|
|
||||||
|
|
||||||
@@ -43,8 +43,10 @@ class ObjectTypeParser(GenericTypeParser):
|
|||||||
:param required_keys: List of required keys in the schema.
|
:param required_keys: List of required keys in the schema.
|
||||||
:return: A Pydantic model class.
|
:return: A Pydantic model class.
|
||||||
"""
|
"""
|
||||||
|
model_config = ConfigDict(validate_assignment=True)
|
||||||
fields = cls._parse_properties(schema, required_keys, **kwargs)
|
fields = cls._parse_properties(schema, required_keys, **kwargs)
|
||||||
return create_model(name, **fields)
|
|
||||||
|
return create_model(name, __config__=model_config, **fields)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_properties(
|
def _parse_properties(
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
from typing_extensions import Dict, List, Literal, TypedDict, Union
|
from typing_extensions import Dict, List, Literal, TypedDict, Union
|
||||||
|
|
||||||
|
from types import NoneType
|
||||||
|
|
||||||
|
|
||||||
JSONSchemaType = Literal[
|
JSONSchemaType = Literal[
|
||||||
"string", "number", "integer", "boolean", "object", "array", "null"
|
"string", "number", "integer", "boolean", "object", "array", "null"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
JSONSchemaNativeTypes: tuple[type, ...] = (
|
||||||
|
str,
|
||||||
|
int,
|
||||||
|
float,
|
||||||
|
bool,
|
||||||
|
list,
|
||||||
|
set,
|
||||||
|
NoneType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]]
|
JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
49
tests/parser/test_const_type_parser.py
Normal file
49
tests/parser/test_const_type_parser.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from jambo.parser import ConstTypeParser
|
||||||
|
|
||||||
|
from typing_extensions import Annotated, get_args, get_origin
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestConstTypeParser(TestCase):
|
||||||
|
def test_const_type_parser(self):
|
||||||
|
parser = ConstTypeParser()
|
||||||
|
|
||||||
|
expected_const_value = "United States of America"
|
||||||
|
properties = {"const": expected_const_value}
|
||||||
|
|
||||||
|
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||||
|
"country", properties
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(get_origin(parsed_type), Annotated)
|
||||||
|
self.assertIn(str, get_args(parsed_type))
|
||||||
|
|
||||||
|
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||||
|
|
||||||
|
def test_const_type_parser_invalid_properties(self):
|
||||||
|
parser = ConstTypeParser()
|
||||||
|
|
||||||
|
expected_const_value = "United States of America"
|
||||||
|
properties = {"notConst": expected_const_value}
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError) as context:
|
||||||
|
parser.from_properties_impl("invalid_country", properties)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
"Const type invalid_country must have 'const' property defined",
|
||||||
|
str(context.exception),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_const_type_parser_invalid_const_value(self):
|
||||||
|
parser = ConstTypeParser()
|
||||||
|
|
||||||
|
properties = {"const": {}}
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError) as context:
|
||||||
|
parser.from_properties_impl("invalid_country", properties)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
"Const type invalid_country must have 'const' value of allowed types",
|
||||||
|
str(context.exception),
|
||||||
|
)
|
||||||
@@ -634,3 +634,26 @@ class TestSchemaConverter(TestCase):
|
|||||||
|
|
||||||
obj = Model()
|
obj = Model()
|
||||||
self.assertEqual(obj.status.value, "active")
|
self.assertEqual(obj.status.value, "active")
|
||||||
|
|
||||||
|
def test_const_type_parser(self):
|
||||||
|
schema = {
|
||||||
|
"title": "Country",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"const": "United States of America",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
Model = SchemaConverter.build(schema)
|
||||||
|
|
||||||
|
obj = Model()
|
||||||
|
self.assertEqual(obj.name, "United States of America")
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
obj.name = "Canada"
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Model(name="Canada")
|
||||||
|
|||||||
Reference in New Issue
Block a user