question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Cerberus 2: Proposal to make handlers 1st class 'citizens' / decouple rules etc. from validator classes

See original GitHub issue

This is a proposal for the next major release of Cerberus that will require users to refactor their custom validators as no backward compatibility is intended.

The current mechanics to extend a Validator respectively the basic mechanics of Cerberus’ dispatching of appropriate implementations of rules, ~type checks~ (see #374), default setters, validators and coercions relies on the structure of callable’s names that are bound to a validator class. Here’s an example that will be used as reference in the following discussion:

class MyValidator(cerberus.Validator):

    def _normalize_coerce_bar(value):
        pass

    def _validate_foo(self, constraint, field, value):
        pass

    def _validate_validator_foo(field, value, error):
        pass

This is generally working well, but it has a few minor drawbacks.

As alternative I propose to decouple the handlers from the validators by making rule implementations etc. first level ‘citizens’ that can then be used to assemble a validator’s functionality:


@coercer()
def bar(value):
    pass


@validation_rule()
def foo(validator, constraint, field, value):
    pass


@validator(name='foo')
def foo_the_validator(validator, field, value):
    pass


class MyValidator(cerberus.Validator):
    wanted_handlers = (bar, foo, foo_the_validator)

This allows reuse of handlers without the need to wrap them in classes in the first place that will then be used as mixin-classes. Grouping of handlers can still be achieved with simple sequence types. But flexibilty is added, a handler can easily be referenced in different groups.

One point that comes up repeatedly with the current design is the confusion of the prefixes _validate and _validate_validator. While the latter seems redundantly named, both names are hardly distinct. This isn’t completely solved in the proposal above as both categories still stem from valid and hey, isn’t this library all about it? Well, at least the entity that can be used as rule is marked with that term.

The example also shows that handlers can be assigned with explicitly given names that ought to be used in schemas.

Admittedly, this one’s a rare case; that one can’t really remove such handler in custom validators. To achieve that one would imitate the absence of that handler in its implementation. Now, with functions that are first level ‘citizens’, one can simply point at them:

class MyOtherValidator(MyValidator):
    unwanted_handlers = (foo_the_validator,)

Another circumstance that can be improved are rule’s dependencies that must currently be defined statically for any validator (e.g. w/ Validator.priority_validations), decoupled from the rules themselves. Annotated functions can do better:


@validation_rule()
def bar(…):
    pass


@validation_rule(after=bar)
def foo(…):
    pass

Similarly, other aspects like mandatoriness can be annotated.

Another annotation would be useful for rules, coercers and validators that are supposed for certain types:

string_type = TypeDefinition('string', (typing.AnyStr), ())

@coercer(type_filter=string_type)
def tokenize(value):
    return value.split(' ')

@validation_rule(type_filter=string_type)
def regex(validator, constraint, value):
    if not re.match(constraint, value):
        validator.error(…)

Finally, a user can use all editor/IDE-fanciness when writing schemas for a rule’s constraint:

@validation_rule(constraint_schema={'type': 'string'})
def regex(…):
    pass

Currently a user must write these annotations into docstrings that will are eventually parsed to dictionaries by Cerberus.

The implementation will take advantage of the fact that functions are objects and thus arbitrary data can be bound to them as properties and further rely on metaclasses, not much different than now.

Paradigmatically this shifts validators from implementing containers ro rather describing containers (while still implementing the core).

A 2to3-like tool that dumps a source file with the transformed methods of a Validator seems not too complicated, but may also result in more effort than some find and replace action.


1st addendum: it should be investigated whether declaring different handlers for the same rule name to be applied in different processing phases is a viable option. a use at hand would be the readonly rule, hence that investigation must turn out positive for the whole story to succeed.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:1
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
nicolaiaroccicommented, Apr 9, 2018

Nice design, but I wonder if it would be worth the effort. The new API would totally diverge from current one, making Cerberus 2 a different product. I wonder how many users would be happy to refactor so heavily. My guess is that most of them would just stay with 1.x. The old adagio “if something works fine, leave it alone” would probably apply here.

Feedback from current users would be valuable here. Anyone?

0reactions
funkyfuturecommented, May 6, 2019

i’m adding the 2.0 milestone here while the inclusion of a proven implementation of the proposal remains undecided (but we have to decide before a 2.0 release).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Extending Cerberus
Though you can use functions in conjunction with the coerce and the check_with rules, you can easily extend the Validator class with custom...
Read more >
Cerberus Documentation - Read the Docs
Cerberus schemas are built with vanilla Python types: dict, list, string, etc. Even user-defined validation rules.
Read more >
A Blockchain-Based Accreditation and Degree Verification ...
In this paper, we propose a comprehensive blockchain-based credential verification solution, Cerberus, which is considerably more efficient, ...
Read more >
ACM CCS 2022
There have been few high-impact deployments of hardware implementations of cryptographic primitives. We present the benefits and challenges of hardware ...
Read more >
Building Trusted Execution Environments - UC Berkeley EECS
First, this thesis motivates why a TEE should not have a fixed threat model by demonstrat- ing a novel off-chip side-channel attack on...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found