Skip to content

Commit

Permalink
refactor(python): Rework public API
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
  • Loading branch information
Stranger6667 committed Sep 18, 2024
1 parent a0a3578 commit 1b6e25a
Show file tree
Hide file tree
Showing 13 changed files with 531 additions and 68 deletions.
3 changes: 3 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ test-rs *FLAGS:
cargo llvm-cov --html test {{FLAGS}}

test-py *FLAGS:
uvx --with="crates/jsonschema-py[tests]" --refresh pytest crates/jsonschema-py/tests-py -rs {{FLAGS}}

test-py-no-rebuild *FLAGS:
uvx --with="crates/jsonschema-py[tests]" pytest crates/jsonschema-py/tests-py -rs {{FLAGS}}

bench-py *FLAGS:
Expand Down
15 changes: 15 additions & 0 deletions crates/jsonschema-py/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## [Unreleased]

### Added

- New draft-specific validator classes: `Draft4Validator`, `Draft6Validator`, `Draft7Validator`, `Draft201909Validator`, and `Draft202012Validator`.
- `validator_for` function for automatic draft detection.

### Changed

- The `JSONSchema` class has been renamed to `Validator`. The old name is retained for backward compatibility but will be removed in a future release.

### Deprecated

- The `JSONSchema` class is deprecated. Use the `validator_for` function or draft-specific validators instead.
You can use `validator_for` instead of `JSONSchema.from_str`.
- Constants `jsonschema_rs.DRAFT4`, `jsonschema_rs.DRAFT6`, `jsonschema_rs.DRAFT7`, `jsonschema_rs.DRAFT201909`, and `jsonschema_rs.DRAFT202012` are deprecated in favor of draft-specific validator classes.

### Fixed

- Location-independent references in remote schemas on drafts 4, 6, and 7.
Expand Down
25 changes: 25 additions & 0 deletions crates/jsonschema-py/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Migration Guide

## Upgrading from 0.19.x to 0.20.0


Draft-specific validators are now available:

```python
# Old (0.19.x)
validator = jsonschema_rs.JSONSchema(schema, draft=jsonschema_rs.Draft202012)

# New (0.20.0)
validator = jsonschema_rs.Draft202012Validator(schema)
```

Automatic draft detection:

```python
# Old (0.19.x)
validator = jsonschema_rs.JSONSchema(schema)

# New (0.20.0)
validator = jsonschema_rs.validator_for(schema)
```

69 changes: 48 additions & 21 deletions crates/jsonschema-py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,34 @@ A high-performance JSON Schema validator for Python.
```python
import jsonschema_rs

validator = jsonschema_rs.JSONSchema({"minimum": 42})
schema = {"maxLength": 5}
instance = "foo"

# Boolean result
validator.is_valid(45)

# Raise a ValidationError
validator.validate(41)
# ValidationError: 41 is less than the minimum of 42
#
# Failed validating "minimum" in schema
#
# On instance:
# 41

# Iterate over all validation errors
for error in validator.iter_errors(40):
# One-off validation
try:
jsonschema_rs.validate(schema, "incorrect")
except jsonschema_rs.ValidationError as exc:
assert str(exc) == '''"incorrect" is longer than 5 characters
Failed validating "maxLength" in schema
On instance:
"incorrect"'''

# Build & reuse (faster)
validator = jsonschema_rs.validator_for(schema)

# Iterate over errors
for error in validator.iter_errors(instance):
print(f"Error: {error}")
print(f"Location: {error.instance_path}")

# Boolean result
assert validator.is_valid(instance)
```

> ⚠️ **Upgrading from pre-0.20.0?** Check our [Migration Guide](MIGRATION.md) for key changes.
## Highlights

- 📚 Support for popular JSON Schema drafts
Expand Down Expand Up @@ -62,20 +71,34 @@ pip install jsonschema-rs

## Usage

If you have a schema as a JSON string, then you could use
`jsonschema_rs.JSONSchema.from_str` to avoid parsing on the
Python side:
If you have a schema as a JSON string, then you could pass it to `validator_for`
to avoid parsing on the Python side:

```python
validator = jsonschema_rs.JSONSchema.from_str('{"minimum": 42}')
validator = jsonschema_rs.validator_for('{"minimum": 42}')
...
```

You can specify a custom JSON Schema draft using the `draft` argument:
You can use draft-specific validators for different JSON Schema versions:

```python
import jsonschema_rs

# Automatic draft detection
validator = jsonschema_rs.validator_for({"minimum": 42})

# Draft-specific validators
validator = jsonschema_rs.Draft7Validator({"minimum": 42})
validator = jsonschema_rs.Draft201909Validator({"minimum": 42})
validator = jsonschema_rs.Draft202012Validator({"minimum": 42})
```

For backwards compatibility, you can still use the `JSONSchema` class with the `draft` argument, but this is deprecated:

```python
import jsonschema_rs

# Deprecated: Use draft-specific validators instead
validator = jsonschema_rs.JSONSchema(
{"minimum": 42},
draft=jsonschema_rs.Draft7
Expand All @@ -99,7 +122,7 @@ def is_currency(value):
return len(value) == 3 and value.isascii()


validator = jsonschema_rs.JSONSchema(
validator = jsonschema_rs.validator_for(
{"type": "string", "format": "currency"},
formats={"currency": is_currency}
)
Expand All @@ -121,6 +144,10 @@ For detailed benchmarks, see our [full performance comparison](BENCHMARKS.md).

`jsonschema-rs` supports CPython 3.8, 3.9, 3.10, 3.11, and 3.12.

## Acknowledgements

This library draws API design inspiration from the Python [`jsonschema`](https://github.com/python-jsonschema/jsonschema) package. We're grateful to the Python `jsonschema` maintainers and contributors for their pioneering work in JSON Schema validation.

## Support

If you have questions, need help, or want to suggest improvements, please use [GitHub Discussions](https://github.com/Stranger6667/jsonschema-rs/discussions).
Expand Down
8 changes: 4 additions & 4 deletions crates/jsonschema-py/benches/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def args(request, variant):
if (schema is OPENAPI or schema is SWAGGER) and variant == "fastjsonschema":
pytest.skip("fastjsonschema does not support the uri-reference format and errors")
if variant == "jsonschema-rs-is-valid":
return jsonschema_rs.JSONSchema(schema).is_valid, instance
return jsonschema_rs.validator_for(schema).is_valid, instance
if variant == "jsonschema-rs-validate":
return jsonschema_rs.JSONSchema(schema).validate, instance
return jsonschema_rs.validator_for(schema).validate, instance
if variant == "jsonschema":
return jsonschema.validators.validator_for(schema)(schema).is_valid, instance
if variant == "fastjsonschema":
Expand All @@ -105,8 +105,8 @@ def args(request, variant):
@pytest.mark.parametrize(
"func",
(
lambda x: jsonschema_rs.JSONSchema(json.loads(x)),
jsonschema_rs.JSONSchema.from_str,
lambda x: jsonschema_rs.validator_for(json.loads(x)),
jsonschema_rs.validator_for,
),
ids=["py-parse", "rs-parse"],
)
Expand Down
1 change: 0 additions & 1 deletion crates/jsonschema-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ classifiers = [
"Topic :: File Formats :: JSON :: JSON Schema",
]
requires-python = ">=3.8"
dependencies = []

[project.optional-dependencies]
tests = [
Expand Down
91 changes: 91 additions & 0 deletions crates/jsonschema-py/python/jsonschema_rs/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,94 @@ Draft6: int
Draft7: int
Draft201909: int
Draft202012: int

class Draft4Validator:
def __init__(
self,
schema: _SchemaT | str,
formats: dict[str, _FormatFunc] | None = None,
) -> None:
pass

def is_valid(self, instance: Any) -> bool:
pass

def validate(self, instance: Any) -> None:
pass

def iter_errors(self, instance: Any) -> Iterator[ValidationError]:
pass

class Draft6Validator:
def __init__(
self,
schema: _SchemaT | str,
formats: dict[str, _FormatFunc] | None = None,
) -> None:
pass

def is_valid(self, instance: Any) -> bool:
pass

def validate(self, instance: Any) -> None:
pass

def iter_errors(self, instance: Any) -> Iterator[ValidationError]:
pass

class Draft7Validator:
def __init__(
self,
schema: _SchemaT | str,
formats: dict[str, _FormatFunc] | None = None,
) -> None:
pass

def is_valid(self, instance: Any) -> bool:
pass

def validate(self, instance: Any) -> None:
pass

def iter_errors(self, instance: Any) -> Iterator[ValidationError]:
pass

class Draft201909Validator:
def __init__(
self,
schema: _SchemaT | str,
formats: dict[str, _FormatFunc] | None = None,
) -> None:
pass

def is_valid(self, instance: Any) -> bool:
pass

def validate(self, instance: Any) -> None:
pass

def iter_errors(self, instance: Any) -> Iterator[ValidationError]:
pass

class Draft202012Validator:
def __init__(
self,
schema: _SchemaT | str,
formats: dict[str, _FormatFunc] | None = None,
) -> None:
pass

def is_valid(self, instance: Any) -> bool:
pass

def validate(self, instance: Any) -> None:
pass

def iter_errors(self, instance: Any) -> Iterator[ValidationError]:
pass

def validator_for(
schema: _SchemaT,
formats: dict[str, _FormatFunc] | None = None,
) -> Draft4Validator | Draft6Validator | Draft7Validator | Draft201909Validator | Draft202012Validator:
pass
Loading

0 comments on commit 1b6e25a

Please sign in to comment.