From b2992209f8aa364d32256cfbf85ca44531c837a5 Mon Sep 17 00:00:00 2001 From: Nuno Pereira Date: Mon, 27 Nov 2023 00:32:39 +0000 Subject: [PATCH 1/3] Added refine method --- .gitignore | 2 ++ CHANGELOG.md | 3 ++- zon/base.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 52acce1..be1ef47 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 318c957..f6fb863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. @@ -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`. diff --git a/zon/base.py b/zon/base.py index 4d8b7e0..6846ba1 100644 --- a/zon/base.py +++ b/zon/base.py @@ -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. @@ -97,6 +101,38 @@ 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: + def _refined_validator(data): + return _clone.validators["_refined_"](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. From 9e90d94dcece151a019f034b739e6dc8c886021e Mon Sep 17 00:00:00 2001 From: Nuno Pereira Date: Mon, 27 Nov 2023 00:44:46 +0000 Subject: [PATCH 2/3] Secured refined function --- zon/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zon/base.py b/zon/base.py index 6846ba1..da5fbd2 100644 --- a/zon/base.py +++ b/zon/base.py @@ -124,8 +124,10 @@ def _refinement_validate(data): if "_refined_" not in _clone.validators: _clone.validators["_refined_"] = _refinement_validate else: + current_refinement = _clone.validators["_refined_"] + def _refined_validator(data): - return _clone.validators["_refined_"](data) and _refinement_validate( + return current_refinement(data) and _refinement_validate( data ) From a9fc5417c36921bf3943f255b9973c4c56476f03 Mon Sep 17 00:00:00 2001 From: Nuno Pereira Date: Mon, 27 Nov 2023 15:06:40 +0000 Subject: [PATCH 3/3] Added basic testing of 'refine' method --- tests/base_test.py | 16 ++++++++++++++++ zon/base.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tests/base_test.py diff --git a/tests/base_test.py b/tests/base_test.py new file mode 100644 index 0000000..7032ec4 --- /dev/null +++ b/tests/base_test.py @@ -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) \ No newline at end of file diff --git a/zon/base.py b/zon/base.py index da5fbd2..a9de5f9 100644 --- a/zon/base.py +++ b/zon/base.py @@ -133,7 +133,7 @@ def _refined_validator(data): _clone.validators["_refined_"] = _refined_validator - return _clone() + return _clone def optional(zon: Zon) -> "ZonOptional": @@ -167,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