Filtering and validation library. Can filter and validate data in objects and simple dictionaries with flexible schemas.
Main idea: decouple filtering and validation rules from web forms into flexible schemas, then reuse those schemas in forms as well as apis and cli. Model validation and filtering rules should be part of the model and your domain logic, not your views or forms logic.
You can use any kind of object or a dictionary as your model. If you use filtering model will be changed in-place by applying the filters you define.
Schema is a collection of rules to filter and validate properties of your
model (object or dictionary). There are several ways to create a schema
most nice being by subclassing Schema
object:
from shiftschema.schema import Schema
from shiftschema import validators as validator
class MySchema(Schema):
def schema(self):
self.add_property('name', required=True)
self.name.add_validator(validator.Length(min=3, max=100))
self.add_property('email')
self.email.add_validator(validator.Email)
schema = MySchema()
You can then use this schema to filter and validate your model data, or process
it (filter and validate as single operation).
To validate a model pass it to your schema and get back Result
:
model = dict(name=None, email='BAD')
valid = schema.process(model)
print(valid == True) # False - validaation failed
print(valid.errors) # errors: name='Required', email='Invalid'
There is a number of common validators provided and you can easily plug your own.
You can attach filters to your schema. Those will be applied in turn and update model data in-place before doing any validations.
person = Person(name=' Morty ', birthyear = 'born in 1900')
schema = Schema({
'properties': {
'name': dict(
filters: [Strip()]
),
'birthyear': dict(
filters: [Digits(to_int=True)]
)
}
})
print(person.name) # 'Morty' (stripped of spaces)
print(person.birthyar) # 1900 (int)
As with validators there are some filters provided and you can easily plug your own.
Validation on a model gets you a Result
objects that evaluates to boolean
True
or False
depending on if it was valid or not:
valid = schema.validate(model) # return shiftschema.result.Result
bool(valid) # True if valid, False otherwise
All the errors the result contains are Error
objects that
simple validators (like Length
)return. You can easily get those errors as string
messages with:
errors_dict = result.get_messages()
All errors are translated, so you can have them in any language supported by passing a locale (defaults to 'en'):
errors_dict = result.get_messages(locale='en') # translate to locale
You can pass translator
and locale
to Result
object manually but
the most easy way to use translations is through globally available
Translator
that exists on the Schema
. Schemas will inject this translator
and locale in to result objects they create. So you can simply:
from shiftschema.schema import Schema
Schema.translator # preconfigured with default translations
Schema.translator.add_.location(path) # but you can add your own
Schema.locale # is 'en' by default
Schema.locale = 'ru' # but you can change that
After setting those you will get errors by default in Russian with your custom translations loaded.
valid = schema.validate(model)
if not valid:
valid.get_messages() # in russian per global setting
valid.get_messages(locale='en') # or whatever you specify
There is a number of implemented filters already and we are constantly adding more. You also can implement your own by extending from AbstractFilter
class. Currently the follwing filters are provided:
Sanitizes HTML input with bleach Usefull when accepting rich-html input from users.
Removes everything from the string leaving just the digits and optionally converts result to integer.
Uses bleach tp parse input text for something that looks like a URL or email and add links to these elements.
Converts incoming string to lowercase. If incoming data is not a string it will be converted to one implicitly.
Removes spaces, newlines or any specified characters either from fron, back or both sides of a string.
Converts incoming string to uppercase. If incoming data is not a string it will be converted to one implicitly.
Checks if provided value exists in an iterable of valid choices provided to constructor.
Validates that passed value consists only of digits.
Validates that passed in value is a valid email. The check is regex only so we don't do any deep MX checks here
Validates an input for being proper length. You can check for minimum length, maximum length or both.
Accepts an interable and ensures every item in it is allowed. Just like choice, but for multiple values.
Accepts an interable and ensures it is not empty
This is used to mark a property as required. Has modifiers to allow values like False
or 0
.
Extension allows you to use schemas to validate wftforms in flask applications. Forms can represent full model data or just a smaller subset of your model. Both filtering and validation will be applied to form data according to rules defined in schema. And you can mix wtf validation with schema validation. Here is a usage typical pattern:
from shiftschema.ext.flask_wtf import Form
from shiftschema.schema import Schema
from shiftschema import validators, filters
from wtforms import StringField, PasswordField
# extend your form from provided mixin
class UserForm(Form):
""" User form used for creating new users """
username = StringField('username')
email = StringField('email')
password = PasswordField('password')
# define a schema
class UserSchema(Schema):
""" User model filtering and validation schema"""
def schema(self):
self.add_property('username', required=True)
self.username.add_filter(filters.Strip())
self.username.add_validator(validators.Length(min=3, max=200))
self.add_property('email', required=True)
self.email.add_filter(filters.Strip())
self.email.add_validator(validators.Length(min=3, max=200))
self.email.add_validator(validators.Email())
self.add_property('password', required=True)
self.password.add_validator(validators.Length(min=3, max=200))
You can now use schema to validate and filter form data in your views. The usage is similar to regular forms and workflow:
@app.route('/register/', methods=['GET', 'POST'])
def register_view(self):
# just remember to tell the form what schema to use
form = UserForm(schema=UserSchema())
if form.validate_on_submit():
user = User(
username=form.username.data,
email=form.email.data,
password=form.password.data
)
user_service.save(user)
return redirect(url_for('thank.you'))
return render_template('user/register.html')