Merge pull request #30 from HideyoshiNakazone/feature/adds-const

Feature/adds const
This commit was merged in pull request #30.
This commit is contained in:
2025-06-23 15:32:37 -03:00
committed by GitHub
9 changed files with 179 additions and 15 deletions

View 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")

View File

@@ -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

View File

@@ -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",

View 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)]

View File

@@ -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

View File

@@ -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(

View File

@@ -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"]]

View 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),
)

View File

@@ -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")