Giter Club home page Giter Club logo

smarie / python-valid8 Goto Github PK

View Code? Open in Web Editor NEW
27.0 4.0 0.0 4.99 MB

Yet another validation lib ;). Provides tools for general-purpose variable validation, function inputs/outputs validation as well as class fields validation. All entry points raise consistent ValidationError including all contextual details, with dynamic inheritance of ValueError/TypeError as appropriate.

Home Page: https://smarie.github.io/python-valid8/

License: BSD 3-Clause "New" or "Revised" License

Python 99.76% Shell 0.24%
python input argument validate validator check value args decorate decorator

python-valid8's Introduction

python-validate (valid8)

"valid8ing is not a crime" ;-)

Python versions Build Status Tests Status codecov

Documentation PyPI Downloads Downloads per week GitHub stars

valid8 provides user-friendly tools for

  • general-purpose inline validation,
  • function inputs/outputs validation
  • class fields validation.

All entry points raise consistent ValidationError including all contextual details, with dynamic inheritance of ValueError/TypeError as appropriate. Originally from the autoclass project.

This is the readme for developers. The documentation for users is available here: https://smarie.github.io/python-valid8/

Want to contribute ?

Contributions are welcome ! Simply fork this project on github, commit your contributions, and create pull requests.

Here is a non-exhaustive list of interesting open topics: https://github.com/smarie/python-valid8/issues

Installing all requirements

In order to install all requirements, including those for tests and packaging, use the following command:

pip install -r ci_tools/requirements-pip.txt

Running the tests

This project uses pytest.

pytest -v valid8/tests/

Packaging

This project uses setuptools_scm to synchronise the version number. Therefore the following command should be used for development snapshots as well as official releases:

python setup.py egg_info bdist_wheel rotate -m.whl -k3

Generating the documentation page

This project uses mkdocs to generate its documentation page. Therefore building a local copy of the doc page may be done using:

mkdocs build -f docs/mkdocs.yml

Generating the test reports

The following commands generate the html test report and the associated badge.

pytest --junitxml=junit.xml -v valid8/tests/
ant -f ci_tools/generate-junit-html.xml
python ci_tools/generate-junit-badge.py

PyPI Releasing memo

This project is now automatically deployed to PyPI when a tag is created. Anyway, for manual deployment we can use:

twine upload dist/* -r pypitest
twine upload dist/*

Merging pull requests with edits - memo

Ax explained in github ('get commandline instructions'):

git checkout -b <git_name>-<feature_branch> master
git pull https://github.com/<git_name>/python-valid8.git <feature_branch> --no-commit --ff-only

if the second step does not work, do a normal auto-merge (do not use rebase!):

git pull https://github.com/<git_name>/python-valid8.git <feature_branch> --no-commit

Finally review the changes, possibly perform some modifications, and commit.

python-valid8's People

Contributors

smarie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

python-valid8's Issues

Adding validation to descriptor fields (including compliance with attrs)

attrs is an alternative to autoclass to create compact classes without boilerplate.

Starting from python 3.6, class attributes can be annotated with PEP484 type hints, so it is quite possible that type checkers such as enforce will be able to validate attrs types (not sure they do right now, though).

However concerning value validation it might be interesting to allow users to plug valid8 on attrs-defined classes.

There are currently two ways to add validators to an attrs-decorated class an per the attrs doc:

@attr.s
class C(object):
    x = attr.ib()
    @x.validator
    def check(self, attribute, value):
        if value > 42:
            raise ValueError("x must be smaller or equal to 42")

or

def check(instance, attribute, value):
    if value > 42:
        raise ValueError("x must be smaller or equal to 42")

@attr.s
class C(object):
    x = attr.ib(validator=check)

It would be quite nice to be able to plug valid8 the same way that @validate_arg works today:

@validate_attr('x', x <= 42, "x must be smaller or equal to 42")
@attr.s
class C(object):
    x = attr.ib()

This can be relatively easy but we have to mess with the setter functions of the attrs descriptors (wrapping them). Is this something that will break attrs behaviour ?

Note that the nice thing is that this would not be specific to attrs, but would work with any class with descriptor fields.

Improve __init__.py

It seems that all should also contain the names of all symbols exported by each module. Let's try to automatically generate this from public names.

Besides, it seems that the symbols imported by the package are re-exported (or at least pycharm thinks so). For example typing symbols, etc. Let's dig this topic too...

ValueError("Keyword context arguments ....")

from valid8 import validate
validate('a', 0, equals=0, help_msg='{details}', details='hello')

leads

>>> ValueError: Keyword context arguments have been provided but help_msg and error_type are not: {'details': 'hello'}

Use of isinstance within wrap_valid provides inconsistent ValueError

with wrap_valid('x', x) as v:
    v.alid = isinstance(x, int) and x >= 0

Always raises a ValueError in case of invalid data, whether x=-1 (value) or x='foo' (type).
We should provide an answer, here are some possible ways

  • provide an alternate checkinstance(x, int) method to raise a subclass of TypeError and demonstrate it in the examples
  • make extensive use of source code introspection in wrap_valid to detect that isinstance was used... not fond of it and it will probably always fail in some cases (starting with terminal input on windows, where we cant inspect the source today)

Ability to disable validation with little or no overhead

Besides the interest of disabling validation in fully trusted environments, this could also be a great tool to help users to compare the execution times of their code with and without valid8 validation, so as to evaluate performance overhead.

lazy import `validation_lib`

The internal validation library should probably not be imported by default. Users interested would import valid8.validation_lib or from valid8.validation_lib import xxx

Bug in typing with python 3.5.2

from valid8 import validate

yields

  File "C:\Users\SESA494757\Software\Anaconda3\envs\clientenv\lib\site-packages\valid8\__init__.py", line 4, in <module>
    from valid8.composition import CompositionFailure, AtLeastOneFailed, and_, DidNotFail, not_, AllValidatorsFailed, or_, \
  File "C:\Users\SESA494757\Software\Anaconda3\envs\clientenv\lib\site-packages\valid8\composition.py", line 23, in <module>
    CallableAndFailureTuple = Tuple[CallableType, Union[str, 'Type[Failure]']]
  File "C:\Users\SESA494757\Software\Anaconda3\envs\clientenv\lib\typing.py", line 552, in __getitem__
    dict(self.__dict__), parameters, _root=True)

Composable failure raiser objects

A failure raiser currently consists in

  • some validation callable
  • a failure type (default Failure)
  • an optional overridden help message

Besides we have composition operators (and, or, etc.)

So we could imagine to provide equivalent objects, directly composable with the python operators :

PositiveInt = InstanceOf(int) & GreaterThan(0)

Advantages:

  • we could compose the type hints automatically (to check if for example pychamr follows the type hints of __and__
  • compacity and reuse

Companion needs/features:

  • if we go down that road, we would need an easy way to define a failure raiser. It could be by blending it with the existing compact syntax, for example
IntMultipleOfTwo = InstanceOf(int) & (lambda x: x % 2 == 0, 'x should be a multiple of 2', CustomType)
  • factories could maybe be defined as classes "ร  la checktypes" ? As :
class MultipleOf(...):
    params = 'nb', 
    base_type = None
    checker = lambda x, nb: x % nb == 0
    help_msg = "x should be multiple of {nb}"

Notes:

  • we should probably not wish these objects to be classes (like in checktypes). That is maybe more elegant conceptualy but would definitely be less efficient in terms of python (metaclasses, overriding instance_check etc.)

Traceback improvements for instance checks

Caught errors are set as the __cause__ of all errors raised by valid8. However in the case of instance_of it seems that it could be safely ignored, as this is redundant information.

from valid8 import validate
x = "hello"
validate('x', x, instance_of=int)

yields

Traceback (most recent call last):
  File "c:\w_dev\_pycharm_workspace\1_tools\python-valid8\valid8\entry_points_inline.py", line 189, in validate
    assert_instance_of(value, instance_of)
  File "c:\w_dev\_pycharm_workspace\1_tools\python-valid8\valid8\entry_points_inline.py", line 34, in assert_instance_of
    raise HasWrongType(wrong_value=value, ref_type=allowed_types)
valid8.validation_lib.types.HasWrongType: Value should be an instance of <class 'int'>. Wrong value: [hello]
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "<input>", line 3, in <module>
  File "c:\w_dev\_pycharm_workspace\1_tools\python-valid8\valid8\entry_points_inline.py", line 259, in validate
    help_msg=help_msg, **kw_context_args)
  File "c:\w_dev\_pycharm_workspace\1_tools\python-valid8\valid8\entry_points_inline.py", line 93, in _create_validation_error
    raise err
valid8.entry_points.ValidationError[TypeError]: Error validating [x=hello]. HasWrongType: Value should be an instance of <class 'int'>. Wrong value: [hello].

Where the 'cause' part could be safely ignored. Thanks to Yahya Abou 'Imran for the suggestion!

Typos are not easily detected

from valid8 import validate
a = 3
validate('a', a, minvalue=5.1, max_value=5.2)  # DOES NOT RAISE ?? that's because minvalue has a typo

Add checktypes in the documentation in the chapter about 3rd party libs

Library checktypes provides a way to include validation in the class definition.

from valid8 import validate
from checktypes import checktype
PositiveInt = checktype('PositiveInt', int, lambda x: x > 0) x = -1 
validate('x', x, custom=PositiveInt.validate)

yields

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/valid8/entry_points.py", line 431, in assert_valid
    res = self.main_function(value)
  File "/usr/lib/python3.6/site-packages/checktypes/checktype.py", line 142, in validate
    raise ValueError(cls._errmsg.format(repr(val)))
ValueError: expected 'PositiveInt' but got -1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/site-packages/valid8/entry_points_inline.py", line 263, in validate
    assert_valid(name, value, custom, error_type=error_type, help_msg=help_msg, **kw_context_args)
  File "/usr/lib/python3.6/site-packages/valid8/entry_points.py", line 555, in assert_valid
    none_policy=none_policy).assert_valid(name=name, value=value, **kw_context_args)
  File "/usr/lib/python3.6/site-packages/valid8/entry_points.py", line 444, in assert_valid
    help_msg=help_msg, **kw_context_args)
valid8.entry_points.ValidationError[ValueError]: Error validating [x=-1]. Validation function [validate] raised ValueError: expected 'PositiveInt' but got -1.

And

validate('x', x, instance_of=PositiveInt)

yields

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/valid8/entry_points_inline.py", line 189, in validate
    assert_instance_of(value, instance_of)
  File "/usr/lib/python3.6/site-packages/valid8/entry_points_inline.py", line 34, in assert_instance_of
    raise HasWrongType(wrong_value=value, ref_type=allowed_types)
valid8.validation_lib.types.HasWrongType: Value should be an instance of <class 'checktypes.checktype.PositiveInt'>. Wrong value: [-1]

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/site-packages/valid8/entry_points_inline.py", line 259, in validate
    help_msg=help_msg, **kw_context_args)
  File "/usr/lib/python3.6/site-packages/valid8/entry_points_inline.py", line 93, in _create_validation_error
    raise err
valid8.entry_points.ValidationError[TypeError]: Error validating [x=-1]. HasWrongType: Value should be an instance of <class 'checktypes.checktype.PositiveInt'>. Wrong value: [-1].

See #14 for traceback improvements of the second example

Error messages improvements : remove the brackets for the details in `Failure`

This validation message is received when checking that '' is non empty

ValidationError[ValueError]: Error validating [f='']. Validation function [non_empty] raised Empty: len(x) > 0 does not hold for x=. Wrong value: [].

It raises the question of "what to display in the message ?" Indeed displaying [] in the end of the message is not satisfying to me. Maybe we should use repr(x) without brackets rather.

ValidationError should not always inherit from `ValueError`, users should be able to decide

It is recommended to use TypeError for type validation and ValueError for value validation, or any subclass of those.

As of today our wrapping ValidationError extends ValueError, which does not make sense if you use it to solely performing type validation.

Solution 1

Remove the inheritance of ValueError in ValidationError . Let the ValueError or TypeError inheritance to the responsibility of inner validation functions if any. For example in case x=None, assert_valid('surface', surf, isfinite) would raise a ValidationError with __cause__ TypeError: must be real number, not NoneType.

For simple validations without inner failure raiser, or if you do not trust the inner failure raiser, you could explicitly define your custom exception inheriting from both ValidationError and the appropriate selection of ValuerError/TypeError, such as

from valid8 import ValidationError, assert_valid

# here we add the inheritance
class InvalidSurfaceError(ValidationError, TypeError, ValueError):
    help_msg="Surface should be a strictly positive finite real number"

from mini_lambda import x
from math import inf

surf = inf
assert_valid('surface', surf, isfinite, x > 0, error_type=InvalidSurfaceError)

Drawback: if you do not redefine the exception as above and only provide a help msg, then the inheritance is lost since plain old ValidationError will be raised, for example in assert_valid('surface', surf, isfinite, x > 0, help_msg="Surface should be a strictly positive finite real number")

Solution 2

Make the inheritance of ValueError/TypeError in ValidationError dynamic depending on the user's choice + Add a kind keyword argument to all API methods, that would be able to take three values TYPE, VALUE, BOTH (default).

assert_valid('surface', surf, isfinite, x > 0, help_msg="Surface should ...", kind=BOTH)

The appropriate subtype of ValidationError/InputValidationError/ClassFieldValidationError , inheriting from ValueError, TypeError, or both, would need to be created. After all that's just 3*3=9 empty classes to write, but there is surely an even better way (using a metaclass?)

But then, how to make custom exceptions still easy to write (having BOTH by default rather than no inheritance)? The metaclass idea is maybe mandatory to reach that end.

To be matured...

Support a dict-like variant to define validation functions

this could be cool

validate('x', x, custom={'x should be finite': (isfinite, NotFinite),
                                 'x should be even': is_even,
                                 'x should be strictly positive': (i > 0)})

or

validate('x', x, custom={isfinite: ('x should be finite', NotFinite),
                                 is_even: 'x should be even',
                                 (i > 0): 'x should be strictly positive'})

Validation should by default fail on None

New entry points (is_valid, assert_valid, Validator) are currently added, and they should fail on None. Besides even for function input validation the current behaviour with not_none is not really satisfying.

So proposal: reverse the logic.

  • By default fail on None everywhere.
  • create an ignore_none wrapper to skip validation when value is None`
  • in Validator constructor as well as in is_valid and assert_valid, allow users to set a flag that will add the wrapper automatically.
  • in @validate and @validate_arg, add the ignore_none wrapper automatically if the function signature has a default value of None or a type hint of Optional[].

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.