Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00802744dd | |||
|
dd2e7d221c
|
|||
|
558abf5d40
|
|||
| 70afa80ccf | |||
|
422cc2efe0
|
|||
|
|
dd31f62ef2 | ||
| e8bda6bc07 | |||
|
d8fe98639a
|
@@ -1,7 +1,7 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.11.4
|
rev: v0.14.7
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
|||||||
- "3.11"
|
- "3.11"
|
||||||
- "3.12"
|
- "3.12"
|
||||||
- "3.13"
|
- "3.13"
|
||||||
|
- "3.14"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -31,7 +32,7 @@ jobs:
|
|||||||
uses: astral-sh/setup-uv@v5
|
uses: astral-sh/setup-uv@v5
|
||||||
with:
|
with:
|
||||||
# Install a specific version of uv.
|
# Install a specific version of uv.
|
||||||
version: "0.6.14"
|
version: "0.9.15"
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: "uv.lock"
|
cache-dependency-glob: "uv.lock"
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.14
|
||||||
@@ -42,8 +42,12 @@ class AnyOfTypeParser(GenericTypeParser):
|
|||||||
# By defining the type as Union of Annotated type we can use the Field validator
|
# By defining the type as Union of Annotated type we can use the Field validator
|
||||||
# to enforce the constraints of each union type when needed.
|
# to enforce the constraints of each union type when needed.
|
||||||
# We use Annotated to attach the Field validators to the type.
|
# We use Annotated to attach the Field validators to the type.
|
||||||
field_types = [
|
field_types = []
|
||||||
Annotated[t, Field(**v)] if v is not None else t for t, v in sub_types
|
for subType, subProp in sub_types:
|
||||||
]
|
default_value = subProp.pop("default", None)
|
||||||
|
if default_value is None:
|
||||||
|
default_value = ...
|
||||||
|
|
||||||
|
field_types.append(Annotated[subType, Field(default_value, **subProp)])
|
||||||
|
|
||||||
return Union[(*field_types,)], mapped_properties
|
return Union[(*field_types,)], mapped_properties
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from jambo.exceptions import InvalidSchemaException
|
|||||||
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 AnyUrl, EmailStr
|
from pydantic import AnyUrl, EmailStr, TypeAdapter, ValidationError
|
||||||
from typing_extensions import Any, Unpack
|
from typing_extensions import Unpack
|
||||||
|
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
from ipaddress import IPv4Address, IPv6Address
|
from ipaddress import IPv4Address, IPv6Address
|
||||||
@@ -62,37 +62,19 @@ class StringTypeParser(GenericTypeParser):
|
|||||||
if format_type in self.format_pattern_mapping:
|
if format_type in self.format_pattern_mapping:
|
||||||
mapped_properties["pattern"] = self.format_pattern_mapping[format_type]
|
mapped_properties["pattern"] = self.format_pattern_mapping[format_type]
|
||||||
|
|
||||||
if "examples" in mapped_properties:
|
try:
|
||||||
mapped_properties["examples"] = [
|
if "examples" in mapped_properties:
|
||||||
self._parse_example(example, format_type, mapped_type)
|
mapped_properties["examples"] = [
|
||||||
for example in mapped_properties["examples"]
|
TypeAdapter(mapped_type).validate_python(example)
|
||||||
]
|
for example in mapped_properties["examples"]
|
||||||
|
]
|
||||||
|
except ValidationError as err:
|
||||||
|
raise InvalidSchemaException(
|
||||||
|
f"Invalid example type for field {name}."
|
||||||
|
) from err
|
||||||
|
|
||||||
if "json_schema_extra" not in mapped_properties:
|
if "json_schema_extra" not in mapped_properties:
|
||||||
mapped_properties["json_schema_extra"] = {}
|
mapped_properties["json_schema_extra"] = {}
|
||||||
mapped_properties["json_schema_extra"]["format"] = format_type
|
mapped_properties["json_schema_extra"]["format"] = format_type
|
||||||
|
|
||||||
return mapped_type, mapped_properties
|
return mapped_type, mapped_properties
|
||||||
|
|
||||||
def _parse_example(
|
|
||||||
self, example: Any, format_type: str, mapped_type: type[Any]
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
Parse example from JSON Schema format to python format
|
|
||||||
:param example: Example Value
|
|
||||||
:param format_type: Format Type
|
|
||||||
:param mapped_type: Type to parse
|
|
||||||
:return: Example parsed
|
|
||||||
"""
|
|
||||||
match format_type:
|
|
||||||
case "date" | "time" | "date-time":
|
|
||||||
return mapped_type.fromisoformat(example)
|
|
||||||
case "duration":
|
|
||||||
# TODO: Implement duration parser
|
|
||||||
raise NotImplementedError
|
|
||||||
case "ipv4" | "ipv6":
|
|
||||||
return mapped_type(example)
|
|
||||||
case "uuid":
|
|
||||||
return mapped_type(example)
|
|
||||||
case _:
|
|
||||||
return example
|
|
||||||
|
|||||||
@@ -128,10 +128,8 @@ class SchemaConverter:
|
|||||||
Gets a cached reference from the reference cache.
|
Gets a cached reference from the reference cache.
|
||||||
:param ref_name: The name of the reference to get.
|
:param ref_name: The name of the reference to get.
|
||||||
:return: The cached reference, or None if not found.
|
:return: The cached reference, or None if not found.
|
||||||
"""
|
"""
|
||||||
cached_type = self._namespace_registry.get(
|
cached_type = self._namespace_registry.get(namespace, {}).get(ref_name)
|
||||||
namespace, {}
|
|
||||||
).get(ref_name)
|
|
||||||
|
|
||||||
if isinstance(cached_type, type):
|
if isinstance(cached_type, type):
|
||||||
return cached_type
|
return cached_type
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from jambo.parser import StringTypeParser
|
|||||||
|
|
||||||
from pydantic import AnyUrl, EmailStr
|
from pydantic import AnyUrl, EmailStr
|
||||||
|
|
||||||
import unittest
|
|
||||||
from datetime import date, datetime, time, timedelta, timezone
|
from datetime import date, datetime, time, timedelta, timezone
|
||||||
from ipaddress import IPv4Address, IPv6Address, ip_address
|
from ipaddress import IPv4Address, IPv6Address, ip_address
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
@@ -121,7 +120,7 @@ class TestStringTypeParser(TestCase):
|
|||||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||||
|
|
||||||
self.assertEqual(type_parsing, AnyUrl)
|
self.assertEqual(type_parsing, AnyUrl)
|
||||||
self.assertEqual(type_validator["examples"], ["test://domain/resource"])
|
self.assertEqual(type_validator["examples"], [AnyUrl("test://domain/resource")])
|
||||||
|
|
||||||
def test_string_parser_with_ip_formats(self):
|
def test_string_parser_with_ip_formats(self):
|
||||||
parser = StringTypeParser()
|
parser = StringTypeParser()
|
||||||
@@ -299,7 +298,6 @@ class TestStringTypeParser(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skip("Duration parsing not yet implemented")
|
|
||||||
def test_string_parser_with_timedelta_format(self):
|
def test_string_parser_with_timedelta_format(self):
|
||||||
parser = StringTypeParser()
|
parser = StringTypeParser()
|
||||||
|
|
||||||
@@ -315,9 +313,9 @@ class TestStringTypeParser(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
type_validator["examples"],
|
type_validator["examples"],
|
||||||
[
|
[
|
||||||
timedelta(days=7),
|
timedelta(days=428, hours=4, minutes=5, seconds=6),
|
||||||
timedelta(minutes=30),
|
timedelta(minutes=30),
|
||||||
timedelta(hours=4, minutes=5, seconds=6),
|
timedelta(days=7),
|
||||||
timedelta(seconds=0.5),
|
timedelta(seconds=0.5),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1109,7 +1109,7 @@ class TestSchemaConverter(TestCase):
|
|||||||
|
|
||||||
def test_namespace_isolation_via_on_call_config(self):
|
def test_namespace_isolation_via_on_call_config(self):
|
||||||
namespace = "namespace1"
|
namespace = "namespace1"
|
||||||
|
|
||||||
schema: JSONSchema = {
|
schema: JSONSchema = {
|
||||||
"$id": namespace,
|
"$id": namespace,
|
||||||
"title": "Person",
|
"title": "Person",
|
||||||
@@ -1130,16 +1130,16 @@ class TestSchemaConverter(TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
model = self.converter.build_with_cache(schema)
|
model = self.converter.build_with_cache(schema)
|
||||||
|
|
||||||
invalid_cached_model = self.converter.get_cached_ref("Person")
|
invalid_cached_model = self.converter.get_cached_ref("Person")
|
||||||
self.assertIsNone(invalid_cached_model)
|
self.assertIsNone(invalid_cached_model)
|
||||||
|
|
||||||
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||||
self.assertIs(model, cached_model)
|
self.assertIs(model, cached_model)
|
||||||
|
|
||||||
def test_clear_namespace_registry(self):
|
def test_clear_namespace_registry(self):
|
||||||
namespace = "namespace_to_clear"
|
namespace = "namespace_to_clear"
|
||||||
|
|
||||||
schema: JSONSchema = {
|
schema: JSONSchema = {
|
||||||
"$id": namespace,
|
"$id": namespace,
|
||||||
"title": "Person",
|
"title": "Person",
|
||||||
@@ -1160,11 +1160,13 @@ class TestSchemaConverter(TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
model = self.converter.build_with_cache(schema)
|
model = self.converter.build_with_cache(schema)
|
||||||
|
|
||||||
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||||
self.assertIs(model, cached_model)
|
self.assertIs(model, cached_model)
|
||||||
|
|
||||||
self.converter.clear_ref_cache(namespace=namespace)
|
self.converter.clear_ref_cache(namespace=namespace)
|
||||||
|
|
||||||
cleared_cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
cleared_cached_model = self.converter.get_cached_ref(
|
||||||
|
"Person", namespace=namespace
|
||||||
|
)
|
||||||
self.assertIsNone(cleared_cached_model)
|
self.assertIsNone(cleared_cached_model)
|
||||||
|
|||||||
Reference in New Issue
Block a user