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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.vscode
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

> This changelog was generated some commits after the [v1.0.0 tag](https://github.com/Naapperas/zon/releases/tag/v1.0.0), so the changelog will have some inconsistencies until the next release.
Expand All @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the `zon.traits.collection` file which contains the `ZonCollection` class: this is the base class for all collection types.
- Added testing for `ZonCollection` and added more tests for `ZonString`.
- Scripts that automate the building and publishing of the package to PyPI.
- Added `refine` method to the base `Zon` class.

### Changed
- `ZonString` now inherits from `ZonCollection` instead of `Zon`.
Expand Down
16 changes: 16 additions & 0 deletions tests/base_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest

import zon

@pytest.fixture
def validator():
return zon.anything()

def test_refine(validator):

assert validator.validate(1)

refined_validator = validator.refine(lambda x: x == 1)

assert refined_validator.validate(1)
assert not refined_validator.validate(2)
40 changes: 39 additions & 1 deletion zon/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
ValidationRule = Callable[[T], bool]


class AggregateValidator:
"""A validator that aggregates multiple validation calls"""


class Zon(ABC):
"""Base class for all Zons.
A Zon is the basic unit of validation in Zon.
Expand Down Expand Up @@ -97,6 +101,40 @@ def and_also(self, zon: "Zon") -> "ZonAnd":
def __and__(self, zon: "Zon") -> "ZonAnd":
return self.and_also(zon)

def refine(self, refinement: Callable[[T], bool]) -> "Self":
"""Creates a new Zon that validates the data with the supplied refinement.

A refinement is a function that takes a piece of data and returns True if the data is valid or throws otherwise.

Args:
refinement (Callable[[T], bool]): the refinement to be applied.

Returns:
ZonRefined: a new validator that validates the data with the supplied refinement.
"""
_clone = self._clone()

def _refinement_validate(data):
try:
return refinement(data)
except ValidationError as e:
_clone._add_error(e)
return False

if "_refined_" not in _clone.validators:
_clone.validators["_refined_"] = _refinement_validate
else:
current_refinement = _clone.validators["_refined_"]

def _refined_validator(data):
return current_refinement(data) and _refinement_validate(
data
)

_clone.validators["_refined_"] = _refined_validator

return _clone


def optional(zon: Zon) -> "ZonOptional":
"""Marks this validation chain as optional, making it so the data supplied need not be defined.
Expand Down Expand Up @@ -129,7 +167,7 @@ def _default_validate(self, data):
class ZonAnd(Zon):
"""A Zon that validates that the data is valid for both this Zon and the supplied Zon."""

def __init__(self, zon1, zon2):
def __init__(self, zon1: Zon, zon2: Zon):
super().__init__()
self.zon1 = zon1
self.zon2 = zon2
Expand Down