Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,21 @@ Both use suffix matching, so you can omit leading path components.

## Supported JSON Schema types

| JSON Schema type | validataclass | dataclass / pydantic |
|-------------------------|-----------------------------------------------------------------|------------------------------------------------------|
| `boolean` | `BooleanValidator()` | `bool` |
| `integer` | `IntegerValidator(min_value=..., ...)` | `int` / `Annotated[int, Field(ge=..., ...)]` |
| `number` | `FloatValidator(min_value=..., ...)` | `float` / `Annotated[float, Field(ge=..., ...)]` |
| `string` | `StringValidator(min_length=..., ...)` | `str` / `Annotated[str, Field(min_length=..., ...)]` |
| `string` with `pattern` | `RegexValidator(pattern=r'...')` | `str` / `Annotated[str, Field(pattern=r'...')]` |
| `enum` | `EnumValidator(EnumClassName)` | `EnumClassName` |
| `array` | `ListValidator(inner_validator)` | `list[inner_type]` |
| `object` | `DataclassValidator(ClassName)` | `ClassName` |
| `$ref` | Resolved to the referenced type with property overrides applied |
| JSON Schema type | validataclass | dataclass | pydantic |
|-------------------------------|-----------------------------------------------------------------|------------------------------------------------------|------------------------------------------------------|
| `boolean` | `BooleanValidator()` | `bool` | `bool` |
| `integer` | `IntegerValidator(min_value=..., ...)` | `int` | `int` / `Annotated[int, Field(ge=..., ...)]` |
| `number` | `FloatValidator(min_value=..., ...)` | `float` | `float` / `Annotated[float, Field(ge=..., ...)]` |
| `string` | `StringValidator(min_length=..., ...)` | `str` | `str` / `Annotated[str, Field(min_length=..., ...)]` |
| `string` with `pattern` | `RegexValidator(pattern=r'...')` | `str` | `Annotated[str, Field(pattern=r'...')]` |
| `string` format `date-time` | `DateTimeValidator()` | `datetime` | `datetime` |
| `string` format `time` | `TimeValidator()` | `time` | `time` |
| `string` format `email` | `EmailValidator()` | `str` | `EmailStr` |
| `string` format `uri` | `UrlValidator()` | `str` | `AnyUrl` |
| `enum` | `EnumValidator(EnumClassName)` | `EnumClassName` | `EnumClassName` |
| `array` | `ListValidator(inner_validator)` | `list[inner_type]` | `list[inner_type]` |
| `object` | `DataclassValidator(ClassName)` | `ClassName` | `ClassName` |
| `$ref` | Resolved to the referenced type with property overrides applied |


## Development
Expand Down
14 changes: 14 additions & 0 deletions dev/build-shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Copyright 2025 binary butterfly GmbH
Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt.
"""

import sys
from pathlib import Path

sys.path.append(str(Path(Path(__file__).parent.parent, 'src'))) # noqa: E402

from schema2classes.scripts.build_shared import main

if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Issues = "https://github.com/datex2-tools/schema2classes/issues"
[dependency-groups]
dev = [
"validataclass~=0.11.0",
"pydantic[email]>=2.0",
"pytest~=9.0.2",
"pytest-cov~=7.1.0",
]
Expand Down Expand Up @@ -134,6 +135,8 @@ line-length = 120
"src/schema2classes/scripts/*" = [
# Don't require __init__.py files
"INP",
# Allow print in CLI scripts
"T201",
]
"dev/*" = [
# Don't require __init__.py files
Expand Down
4 changes: 4 additions & 0 deletions src/schema2classes/common/uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ def from_reference(cls, uri: 'URI', reference: str) -> 'URI':
if reference.startswith('http'):
return cls(url=location, json_path=json_path)

# Internal reference (e.g. "#/$defs/Foo"): same file as the parent URI
if location == '':
return cls(file_path=uri.file_path, url=uri.url, json_path=json_path)

# TODO: local file path in a schema via URL
if location.startswith('/'):
return cls(file_path=Path(location), json_path=json_path)
Expand Down
40 changes: 40 additions & 0 deletions src/schema2classes/output/base_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
Array,
BaseField,
Boolean,
DateTime,
Email,
Enum,
Integer,
Number,
Object,
Reference,
String,
Time,
Uri,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -203,6 +207,34 @@ def get_type() -> str:
return 'str'


@dataclass(kw_only=True, init=False)
class DateTimeBaseOutput(BaseOutput, ABC):
@staticmethod
def get_type() -> str:
return 'datetime'


@dataclass(kw_only=True, init=False)
class TimeBaseOutput(BaseOutput, ABC):
@staticmethod
def get_type() -> str:
return 'time'


@dataclass(kw_only=True, init=False)
class EmailBaseOutput(BaseOutput, ABC):
@staticmethod
def get_type() -> str:
return 'str'


@dataclass(kw_only=True, init=False)
class UriBaseOutput(BaseOutput, ABC):
@staticmethod
def get_type() -> str:
return 'str'


@dataclass(kw_only=True, init=False)
class ListBaseOutput(BaseOutput, ABC):
output: BaseOutput
Expand Down Expand Up @@ -367,6 +399,14 @@ def determine_output(field: BaseField, output_classes: dict) -> type[BaseOutput]
return output_classes['integer']
if isinstance(field, Number):
return output_classes['float']
if isinstance(field, DateTime):
return output_classes['datetime']
if isinstance(field, Time):
return output_classes['time']
if isinstance(field, Email):
return output_classes['email']
if isinstance(field, Uri):
return output_classes['uri']
if isinstance(field, String) and field.pattern is not None:
return output_classes['regex']
if isinstance(field, String) and field.pattern is None:
Expand Down
30 changes: 30 additions & 0 deletions src/schema2classes/output/dataclass_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from .base_outputs import (
BooleanBaseOutput,
DateTimeBaseOutput,
EmailBaseOutput,
EnumBaseOutput,
FloatBaseOutput,
IntegerBaseOutput,
Expand All @@ -20,6 +22,8 @@
ObjectBaseOutput,
RegexBaseOutput,
StringBaseOutput,
TimeBaseOutput,
UriBaseOutput,
)


Expand Down Expand Up @@ -79,6 +83,28 @@ class DataclassRegexOutput(DataclassRenderMixin, RegexBaseOutput):
pass


@dataclass(kw_only=True, init=False)
class DataclassDateTimeOutput(DataclassRenderMixin, DateTimeBaseOutput):
def get_imports(self) -> list[str]:
return ['datetime.datetime']


@dataclass(kw_only=True, init=False)
class DataclassTimeOutput(DataclassRenderMixin, TimeBaseOutput):
def get_imports(self) -> list[str]:
return ['datetime.time']


@dataclass(kw_only=True, init=False)
class DataclassEmailOutput(DataclassRenderMixin, EmailBaseOutput):
pass


@dataclass(kw_only=True, init=False)
class DataclassUriOutput(DataclassRenderMixin, UriBaseOutput):
pass


@dataclass(kw_only=True, init=False)
class DataclassListOutput(DataclassRenderMixin, ListBaseOutput):
def get_imports(self) -> list[str]:
Expand All @@ -105,6 +131,10 @@ def get_imports(self) -> list[str]:
'integer': DataclassIntegerOutput,
'float': DataclassFloatOutput,
'string': DataclassStringOutput,
'datetime': DataclassDateTimeOutput,
'time': DataclassTimeOutput,
'email': DataclassEmailOutput,
'uri': DataclassUriOutput,
'enum': DataclassEnumOutput,
'regex': DataclassRegexOutput,
'list': DataclassListOutput,
Expand Down
4 changes: 4 additions & 0 deletions src/schema2classes/output/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .base_outputs import ( # noqa: F401
BaseOutput,
BooleanBaseOutput,
DateTimeBaseOutput,
EmailBaseOutput,
EnumBaseOutput,
FloatBaseOutput,
IntegerBaseOutput,
Expand All @@ -14,6 +16,8 @@
ObjectBaseOutput,
RegexBaseOutput,
StringBaseOutput,
TimeBaseOutput,
UriBaseOutput,
determine_output,
follow_reference,
)
Expand Down
40 changes: 40 additions & 0 deletions src/schema2classes/output/pydantic_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from .base_outputs import (
BooleanBaseOutput,
DateTimeBaseOutput,
EmailBaseOutput,
EnumBaseOutput,
FloatBaseOutput,
IntegerBaseOutput,
Expand All @@ -20,6 +22,8 @@
ObjectBaseOutput,
RegexBaseOutput,
StringBaseOutput,
TimeBaseOutput,
UriBaseOutput,
)


Expand Down Expand Up @@ -162,6 +166,38 @@ def get_imports(self) -> list[str]:
return ['typing.Annotated', 'pydantic.Field']


@dataclass(kw_only=True, init=False)
class PydanticDateTimeOutput(PydanticRenderMixin, DateTimeBaseOutput):
def get_imports(self) -> list[str]:
return ['datetime.datetime']


@dataclass(kw_only=True, init=False)
class PydanticTimeOutput(PydanticRenderMixin, TimeBaseOutput):
def get_imports(self) -> list[str]:
return ['datetime.time']


@dataclass(kw_only=True, init=False)
class PydanticEmailOutput(PydanticRenderMixin, EmailBaseOutput):
@staticmethod
def get_type() -> str:
return 'EmailStr'

def get_imports(self) -> list[str]:
return ['pydantic.EmailStr']


@dataclass(kw_only=True, init=False)
class PydanticUriOutput(PydanticRenderMixin, UriBaseOutput):
@staticmethod
def get_type() -> str:
return 'AnyUrl'

def get_imports(self) -> list[str]:
return ['pydantic.AnyUrl']


@dataclass(kw_only=True, init=False)
class PydanticListOutput(PydanticRenderMixin, ListBaseOutput):
def get_imports(self) -> list[str]:
Expand Down Expand Up @@ -190,6 +226,10 @@ def get_imports(self) -> list[str]:
'integer': PydanticIntegerOutput,
'float': PydanticFloatOutput,
'string': PydanticStringOutput,
'datetime': PydanticDateTimeOutput,
'time': PydanticTimeOutput,
'email': PydanticEmailOutput,
'uri': PydanticUriOutput,
'enum': PydanticEnumOutput,
'regex': PydanticRegexOutput,
'list': PydanticListOutput,
Expand Down
44 changes: 44 additions & 0 deletions src/schema2classes/output/validataclass_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from .base_outputs import (
BooleanBaseOutput,
DateTimeBaseOutput,
EmailBaseOutput,
EnumBaseOutput,
FloatBaseOutput,
IntegerBaseOutput,
Expand All @@ -21,6 +23,8 @@
ObjectBaseOutput,
RegexBaseOutput,
StringBaseOutput,
TimeBaseOutput,
UriBaseOutput,
)


Expand Down Expand Up @@ -131,6 +135,42 @@ def get_imports(self) -> list[str]:
return self._get_base_imports() + ['validataclass.validators.RegexValidator']


@dataclass(kw_only=True, init=False)
class ValidataclassDateTimeOutput(ValidataclassRenderMixin, DateTimeBaseOutput):
def render_validator(self) -> str:
return 'DateTimeValidator()'

def get_imports(self) -> list[str]:
return self._get_base_imports() + ['datetime.datetime', 'validataclass.validators.DateTimeValidator']


@dataclass(kw_only=True, init=False)
class ValidataclassTimeOutput(ValidataclassRenderMixin, TimeBaseOutput):
def render_validator(self) -> str:
return 'TimeValidator()'

def get_imports(self) -> list[str]:
return self._get_base_imports() + ['datetime.time', 'validataclass.validators.TimeValidator']


@dataclass(kw_only=True, init=False)
class ValidataclassEmailOutput(ValidataclassRenderMixin, EmailBaseOutput):
def render_validator(self) -> str:
return 'EmailValidator()'

def get_imports(self) -> list[str]:
return self._get_base_imports() + ['validataclass.validators.EmailValidator']


@dataclass(kw_only=True, init=False)
class ValidataclassUriOutput(ValidataclassRenderMixin, UriBaseOutput):
def render_validator(self) -> str:
return 'UrlValidator()'

def get_imports(self) -> list[str]:
return self._get_base_imports() + ['validataclass.validators.UrlValidator']


@dataclass(kw_only=True, init=False)
class ValidataclassListOutput(ValidataclassRenderMixin, ListBaseOutput):
def render_validator(self) -> str:
Expand Down Expand Up @@ -166,6 +206,10 @@ def get_imports(self) -> list[str]:
'integer': ValidataclassIntegerOutput,
'float': ValidataclassFloatOutput,
'string': ValidataclassStringOutput,
'datetime': ValidataclassDateTimeOutput,
'time': ValidataclassTimeOutput,
'email': ValidataclassEmailOutput,
'uri': ValidataclassUriOutput,
'enum': ValidataclassEnumOutput,
'regex': ValidataclassRegexOutput,
'list': ValidataclassListOutput,
Expand Down
Loading
Loading