Skip to content

Commit cc93e95

Browse files
authored
Merge pull request #9 from datex2-tools/deployment-improvements
deployment improvements
2 parents 18a5a94 + ffdd04b commit cc93e95

10 files changed

Lines changed: 504 additions & 157 deletions

File tree

.github/workflows/build-publish.yml

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ jobs:
1515
steps:
1616
- uses: actions/checkout@v6
1717

18+
- name: install uv
19+
uses: astral-sh/setup-uv@v6
20+
1821
# We use Python 3.12 here because it's the minimum Python version supported by this library.
1922
- name: Setup Python 3.12
20-
uses: actions/setup-python@v6
21-
with:
22-
python-version: '3.12'
23-
24-
- name: Install dependencies
25-
run: pip install --upgrade pip build
23+
run: uv python install 3.12
2624

2725
- name: Build package
28-
run: python -m build
26+
run: uv build
2927

3028
- name: Upload build artifacts
3129
uses: actions/upload-artifact@v7
@@ -34,7 +32,7 @@ jobs:
3432
path: dist/
3533

3634
test:
37-
# This job tests the built package by installing it via pip and running unit tests (without tox).
35+
# This job tests the built package by installing it via pip and running unit tests.
3836
name: Test package
3937
needs: build
4038
runs-on: ubuntu-latest
@@ -47,25 +45,23 @@ jobs:
4745
with:
4846
timezoneLinux: "Europe/Berlin"
4947

48+
- name: install uv
49+
uses: astral-sh/setup-uv@v6
50+
5051
- name: Setup Python 3.12
51-
uses: actions/setup-python@v6
52-
with:
53-
python-version: '3.12'
52+
run: uv python install 3.12
5453

5554
- name: Download build artifacts
5655
uses: actions/download-artifact@v8
5756
with:
5857
name: dist_packages
5958
path: dist/
6059

61-
- name: Install test dependencies
62-
run: pip install -r requirements.txt -r requirements-dev.txt
63-
64-
- name: Install built package
65-
run: pip install dist/schema2validataclass-*.whl
60+
- name: Install built package and test dependencies
61+
run: uv sync --group dev && uv pip install --python .venv dist/schema2validataclass-*.whl --force-reinstall --no-deps
6662

6763
- name: Run unit tests
68-
run: python -m pytest
64+
run: uv run pytest
6965

7066
publish:
7167
name: Publish package

.github/workflows/lint-test.yml

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,17 @@ jobs:
1515
- name: checkout
1616
uses: actions/checkout@v6
1717

18-
- name: setup Python v3.12
19-
uses: actions/setup-python@v6
20-
with:
21-
python-version: '3.12'
22-
cache: 'pip'
18+
- name: install uv
19+
uses: astral-sh/setup-uv@v6
2320

24-
- name: pip install
25-
run: pip install -r requirements.txt -r requirements-dev.txt
21+
- name: setup Python v3.12
22+
run: uv python install 3.12
2623

2724
- name: lint using ruff
28-
# We could also use the official GitHub Actions integration.
29-
# https://beta.ruff.rs/docs/usage/#github-action
30-
# uses: chartboost/ruff-action@v1
31-
run: ruff check --output-format github ./src ./tests
25+
run: uv run ruff check --output-format github ./src ./tests
3226

3327
- name: format check using ruff
34-
# We could also use the official GitHub Actions integration.
35-
# https://beta.ruff.rs/docs/usage/#github-action
36-
# uses: chartboost/ruff-action@v1
37-
run: |
38-
ruff format --check ./src ./tests
28+
run: uv run ruff format --check ./src ./tests
3929

4030
test:
4131
runs-on: ubuntu-latest
@@ -50,14 +40,11 @@ jobs:
5040
- name: checkout
5141
uses: actions/checkout@v6
5242

53-
- name: setup Python v3.12
54-
uses: actions/setup-python@v6
55-
with:
56-
python-version: '3.12'
57-
cache: 'pip'
43+
- name: install uv
44+
uses: astral-sh/setup-uv@v6
5845

59-
- name: pip install
60-
run: pip install -r requirements.txt -r requirements-dev.txt
46+
- name: setup Python v3.12
47+
run: uv python install 3.12
6148

6249
- name: run pytest
63-
run: python -m pytest tests
50+
run: uv run --group dev pytest tests

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repos:
77
- id: check-yaml
88

99
- repo: https://github.com/astral-sh/ruff-pre-commit
10-
rev: v0.15.6
10+
rev: v0.15.9
1111
hooks:
1212
- id: ruff-format
1313
- id: ruff

README.md

Lines changed: 101 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
# schema2validataclass
22

3-
A Python code generator that transforms [JSON Schema](https://json-schema.org/) definitions into Python [`@validataclass`](https://github.com/binary-butterfly/validataclass)-decorated dataclasses or plain `@dataclass` classes, along with Enum classes.
3+
A Python code generator that transforms [JSON Schema](https://json-schema.org/) definitions into Python [`@validataclass`](https://github.com/binary-butterfly/validataclass)-decorated dataclasses, plain `@dataclass` classes, or [Pydantic](https://docs.pydantic.dev/) `BaseModel` classes, along with Enum classes.
4+
45

56
## Features
67

78
- Parses JSON Schema files, including `$ref` references across multiple files and remote schemas (HTTP)
8-
- Generates `@validataclass`-decorated dataclasses with typed validator fields, or plain `@dataclass` classes
9+
- Three output formats: `@validataclass` (with validators), plain `@dataclass`, or Pydantic `BaseModel`
910
- Generates Python `Enum` classes for JSON Schema `enum` types
1011
- Supports nested objects, arrays, references with property overrides, and schema inheritance
1112
- Handles required vs. optional fields with configurable `UnsetValue` / `None` defaults
1213
- Resolves schema dependency graphs automatically by following `$ref` chains
1314
- Detects and breaks circular `$ref` references to prevent import cycles
15+
- Ignore specific references or schema paths to exclude unwanted types from output
1416
- Configurable via YAML configuration file
1517

1618
## Requirements
@@ -19,14 +21,23 @@ A Python code generator that transforms [JSON Schema](https://json-schema.org/)
1921
- [Jinja2](https://jinja.palletsprojects.com/) (template rendering)
2022
- [PyYAML](https://pyyaml.org/) (configuration file parsing)
2123
- [Ruff](https://docs.astral.sh/ruff/) (post-processing of generated files)
22-
- [validataclass](https://github.com/binary-butterfly/validataclass) (used in generated code, not needed for `@dataclass` output)
24+
- [validataclass](https://github.com/binary-butterfly/validataclass) (used in generated code, only needed for `validataclass` output)
25+
- [Pydantic](https://docs.pydantic.dev/) (used in generated code, only needed for `pydantic` output)
26+
2327

2428
## Installation
2529

30+
```bash
31+
uv add schema2validataclass
32+
```
33+
34+
Or with pip:
35+
2636
```bash
2737
pip install schema2validataclass
2838
```
2939

40+
3041
## Usage
3142

3243
```bash
@@ -52,9 +63,24 @@ This reads the schema, recursively resolves all `$ref` references to other schem
5263
- One Python file per `enum` type (e.g. `day_enum.py`)
5364
- One Python file per `object` type (e.g. `closure_information_input.py`)
5465

55-
### Generated output example
5666

57-
Given a JSON Schema object with optional boolean and string fields, the generator produces:
67+
### Generated output examples
68+
69+
Given a JSON Schema object with optional boolean and string fields, the generator produces different output depending on the configured `output_format`.
70+
71+
For enum schemas, the output is the same across all formats:
72+
73+
```python
74+
from enum import Enum
75+
76+
class DayEnum(Enum):
77+
MONDAY = "monday"
78+
TUESDAY = "tuesday"
79+
WEDNESDAY = "wednesday"
80+
```
81+
82+
83+
#### `validataclass` output (default)
5884

5985
```python
6086
from validataclass.validators import StringValidator, BooleanValidator
@@ -69,29 +95,17 @@ class ClosureInformationInput(ValidataclassMixin):
6995
closedFrom: str | UnsetValueType = StringValidator(), Default(UnsetValue)
7096
```
7197

72-
For enum schemas:
73-
74-
```python
75-
from enum import Enum
76-
77-
class DayEnum(Enum):
78-
MONDAY = "monday"
79-
TUESDAY = "tuesday"
80-
WEDNESDAY = "wednesday"
81-
```
98+
This is the default output format. It uses the [`validataclass`](https://github.com/binary-butterfly/validataclass) library for runtime validation. Optional fields use `UnsetValue` by default (configurable via `unset_value_output`). Classes optionally inherit from `ValidataclassMixin` (configurable via `set_validataclass_mixin`).
8299

83-
### `@dataclass` output
84100

85-
Instead of generating `@validataclass`-decorated classes, the generator can produce plain Python `@dataclass` classes. This is useful when you don't need runtime validation and want lightweight data containers with no external dependencies beyond the standard library.
101+
#### `dataclass` output
86102

87103
Set `output_format: dataclass` in your config file (see [Configuration](#configuration) below):
88104

89105
```yaml
90106
output_format: dataclass
91107
```
92108
93-
The same schema that produces the validataclass example above generates:
94-
95109
```python
96110
from dataclasses import dataclass
97111

@@ -102,14 +116,27 @@ class ClosureInformationInput:
102116
closedFrom: str | None = None
103117
```
104118
105-
Key differences from `@validataclass` output:
119+
This produces plain Python `@dataclass` classes with no external dependencies. Optional fields default to `None`, required fields are bare type annotations. The `set_validataclass_mixin` and `unset_value_output` config options have no effect.
120+
121+
122+
#### `pydantic` output
123+
124+
Set `output_format: pydantic` in your config file:
125+
126+
```yaml
127+
output_format: pydantic
128+
```
106129

107-
- Uses Python's built-in `@dataclass(kw_only=True)` decorator
108-
- Fields are plain type annotations without validators
109-
- Optional fields default to `None` instead of `UnsetValue`
110-
- Required fields are bare type annotations (e.g. `name: str`)
111-
- No dependency on the `validataclass` package
112-
- `set_validataclass_mixin` and `unset_value_output` config options have no effect
130+
```python
131+
from pydantic import BaseModel
132+
133+
class ClosureInformationInput(BaseModel):
134+
permananentlyClosed: bool | None = None
135+
temporarilyClosed: bool | None = None
136+
closedFrom: str | None = None
137+
```
138+
139+
This produces [Pydantic V2](https://docs.pydantic.dev/) `BaseModel` classes. Constraints like `minimum`, `maximum`, `minLength`, `maxLength`, and `pattern` are rendered using `Annotated[type, Field(...)]`. Properties that conflict with Python reserved words are renamed with a trailing underscore and a `@model_validator(mode='before')` is generated to map the original names. The `set_validataclass_mixin` and `unset_value_output` config options have no effect.
113140

114141
### Loop detection
115142

@@ -127,6 +154,7 @@ Loop detection is enabled by default. To disable it:
127154
detect_looping_references: false
128155
```
129156

157+
130158
## Configuration
131159

132160
The generator can be configured via a YAML file passed with the `-c` / `--config` flag:
@@ -146,67 +174,87 @@ detect_looping_references: true
146174
post_processing:
147175
- ruff-format
148176
- ruff-check
149-
ignored_uris: []
177+
ignore_references: []
178+
ignore_paths: []
179+
renamed_properties: []
150180
header: |
151181
"""
152182
Custom copyright header.
153183
"""
154184
```
155185

186+
156187
### Options
157188

158-
| Option | Default | Description |
159-
|-----------------------------|-----------------------------|--------------------------------------------------------------------------------------------------------|
160-
| `output_format` | `validataclass` | Output style: `validataclass` (with validators) or `dataclass` (plain Python dataclasses) |
161-
| `unset_value_output` | `UNSET_VALUE` | How optional fields are represented: `UNSET_VALUE` (uses `UnsetValue`) or `NONE` (uses `None`) |
162-
| `object_postfix` | `'Input'` | Suffix appended to generated class names (e.g. `ClosureInformation` becomes `ClosureInformationInput`) |
163-
| `set_validataclass_mixin` | `true` | Whether generated validataclass classes inherit from `ValidataclassMixin` |
164-
| `detect_looping_references` | `true` | Detect and remove circular `$ref` chains to prevent import cycles |
165-
| `post_processing` | `[ruff-format, ruff-check]` | Post-processing steps to run on generated files |
166-
| `ignored_uris` | `[]` | List of field URI paths to skip during generation |
167-
| `header` | Copyright header | Python file header prepended to every generated file |
189+
| Option | Default | Description |
190+
|-----------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------|
191+
| `output_format` | `validataclass` | Output style: `validataclass`, `dataclass`, or `pydantic` |
192+
| `unset_value_output` | `UNSET_VALUE` | How optional fields are represented: `UNSET_VALUE` (uses `UnsetValue`) or `NONE` (uses `None`). Validataclass only. |
193+
| `object_postfix` | `'Input'` | Suffix appended to generated class names (e.g. `ClosureInformation` becomes `ClosureInformationInput`) |
194+
| `set_validataclass_mixin` | `true` | Whether generated validataclass classes inherit from `ValidataclassMixin`. Validataclass only. |
195+
| `detect_looping_references` | `true` | Detect and remove circular `$ref` chains to prevent import cycles |
196+
| `post_processing` | `[ruff-format, ruff-check]` | Post-processing steps to run on generated files |
197+
| `ignore_references` | `[]` | List of `$ref` target URIs to ignore (suffix match). Properties referencing these are removed from their parent. |
198+
| `ignore_paths` | `[]` | List of schema paths to ignore (suffix match). The property at the given path is removed during loading. |
199+
| `renamed_properties` | Python keywords | List of property names that get a trailing `_` to avoid conflicts. Defaults to all Python reserved keywords. |
200+
| `header` | Copyright header | Python file header prepended to every generated file |
201+
202+
203+
### `ignore_references` vs `ignore_paths`
204+
205+
Both options remove properties from the generated output, but they match differently:
206+
207+
- **`ignore_references`** matches the **target** of a `$ref`. For example, `third_schema.json#/definitions/IgnoredObject` removes every property that references that definition, regardless of where the property appears.
208+
- **`ignore_paths`** matches the **location** of a property in the schema. For example, `second_schema.json#/definitions/SecondObject/properties/IgnoredObject` removes only that specific property from `SecondObject`, even if other objects also reference the same definition.
209+
210+
Both use suffix matching, so you can omit leading path components.
211+
168212

169213
## Supported JSON Schema types
170214

171-
| JSON Schema type | Generated validator |
172-
|-------------------------|-----------------------------------------------------------------|
173-
| `boolean` | `BooleanValidator()` |
174-
| `integer` | `IntegerValidator(min_value=..., max_value=...)` |
175-
| `number` | `FloatValidator(min_value=..., max_value=...)` |
176-
| `string` | `StringValidator(min_length=..., max_length=...)` |
177-
| `string` with `pattern` | `RegexValidator(pattern=r'...')` |
178-
| `enum` | `EnumValidator(EnumClassName)` |
179-
| `array` | `ListValidator(inner_validator)` |
180-
| `object` | `DataclassValidator(ClassName)` |
215+
| JSON Schema type | validataclass | dataclass / pydantic |
216+
|-------------------------|-----------------------------------------------------------------|------------------------------------------------------|
217+
| `boolean` | `BooleanValidator()` | `bool` |
218+
| `integer` | `IntegerValidator(min_value=..., ...)` | `int` / `Annotated[int, Field(ge=..., ...)]` |
219+
| `number` | `FloatValidator(min_value=..., ...)` | `float` / `Annotated[float, Field(ge=..., ...)]` |
220+
| `string` | `StringValidator(min_length=..., ...)` | `str` / `Annotated[str, Field(min_length=..., ...)]` |
221+
| `string` with `pattern` | `RegexValidator(pattern=r'...')` | `str` / `Annotated[str, Field(pattern=r'...')]` |
222+
| `enum` | `EnumValidator(EnumClassName)` | `EnumClassName` |
223+
| `array` | `ListValidator(inner_validator)` | `list[inner_type]` |
224+
| `object` | `DataclassValidator(ClassName)` | `ClassName` |
181225
| `$ref` | Resolved to the referenced type with property overrides applied |
182226

227+
183228
## Development
184229

230+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management.
231+
185232
```bash
186-
# Install with development dependencies
187-
pip install -e ".[testing]"
233+
# Sync project with dev dependencies
234+
uv sync --group dev
188235
```
189236

190237
For running without installing, use the development script which adds `src/` to the Python path:
191238

192239
```bash
193-
python dev/run.py <schema_path> <output_path>
240+
uv run python dev/run.py <schema_path> <output_path>
194241
```
195242

196243
```bash
197244
# Lint
198-
ruff check .
245+
uv run ruff check .
199246
200247
# Format
201-
ruff format .
248+
uv run ruff format .
202249
203250
# Run pre-commit hooks
204251
pre-commit run --all-files
205252
206253
# Run tests
207-
pytest
254+
uv run pytest
208255
```
209256

257+
210258
## License
211259

212260
MIT - see LICENSE.txt for details.

0 commit comments

Comments
 (0)