froshki 0.4.3
simple and poor object data mapper library
License: BSD
Keywords: form forms object data mapper schema validation

Froshki
=======

Froshki is a simple and poor object data mapper library.

Looking quite similar to `WTForms
<http://wtforms.simplecodes.com/>`_,
rather intended to focus on data input/output abstraction,
separating validation or conversion functions as APIs & extensions.

Instead of integrating with web forms etc., designed to achieve more flexible attribute sourcing.

Features
........

* Define data schema as class definition.
* Supply data inputs by keyword arguments, dict, or both.
* Convert and validate data inputs with user defined methods or built-in integration with 3rd party libraries.
* Access validated data as attributes / mapping.
* Easy to hook your functions on validation.

Simple usage
------------

Primitive demo:: 

    >>> from froshki import Froshki, Attribute
    >>>
    >>> class ResourceId(Attribute):
    ...     @classmethod
    ...     def transform(klass, input_value):
    ...         return int(input_value)
    ...     @classmethod
    ...     def validate(klass, input_value):
    ...         if input_value in (1,5,7,9):
    ...             return True, input_value
    ...         else:
    ...             return False, 'resource id not found'
    >>>
    >>> class Filetype(Attribute):
    ...     @classmethod
    ...     def transform(klass, input_value):
    ...         return input_value.lower()
    ...     @classmethod
    ...     def validate(klass, input_value):
    ...         if input_value in ('pdf', 'txt', 'mobi'):
    ...             return True, input_value
    ...         else:
    ...             return False, 'filetype unavailable'
    >>>
    >>> class Download(Froshki):
    ...     resource_id = ResourceId()
    ...     filetype = Filetype()
    >>>
    >>> download = Download(resource_id='9', filetype='PDF')
    >>> download.validate()
    True
    >>> download.resource_id
    9
    >>> download.filetype
    'pdf'
 
To use any functions of Froshki, extend ``froshki.Froshki`` to define data model schema.
Attributes are represented by attaching ``froshki.Attribute`` subclasses onto the model.

You can add any data conversion (``Attribute.transform``) or validation (``Attribute.validate``) methods for attributes.
``Froshki.validate`` converts and validates all attributes as defined.
But it's a bit bothersome, and you can use a built-in extension supporting attribute definition.

Using trafaret extension
------------------------

You need to pre-install `trafaret
<https://github.com/Deepwalker/trafaret>`_ to use the extension.
Usage::

    >>> from froshki import Froshki
    >>> import trafaret
    >>> from froshki.ext import trafaret_attr
    >>>
    >>> class SendInquiry(Froshki):
    ...     user_name = trafaret_attr(trafaret.String())()
    ...     user_contact = trafaret_attr(trafaret.Email())()
    ...     message = trafaret_attr(trafaret.String(regex=r'\w{10,400}'))()
    >>>
    >>> send_inquiry = SendInquiry(
    ...     user_name='yu mat', user_contact='drowse314@gmail.com',
    ...     message='cannot post messages to my group'
    ... )
    >>> send_inquiry.validate()
    True

If you prefer other validation libraries,
you will find it so easy to extend ``froshki.Attribute.validate``.
Or some more libraries are built-in supported:

* `voluptuous <https://github.com/alecthomas/voluptuous>`_

See ``froshki/ext/*_attr.py`` for documentation or details of extension wrinting.

Other features
--------------

Data as mappings
................

Some utility properties are available for accessing validated data::

    (...)
    >>> send_inquiry.data
    {'user_name': 'yu mat', 'user_contact': 'drowse314@gmail.com', 'message': 'cannot post messages to my group'}
    >>> send_inquiry.errors  # error messages are registered if validation failed
    {}

Further, you can initialize ``froshki.Froshki`` with mappings::

    (...)
    >>> data = {'user_name': 'ymat', 'user_contact': 'drowse314.gmail.com', 'message': 'cannot post messages to my group'}
    >>> another_inquiry = SendInquiry(source=data)
    >>> another_inquiry.validate()
    False

Source attributes with alias names
..................................

You can use the names differring from the class attribute names for sourcing attributes::

    >>> class ResourceAccess(Froshki):
    ...     resource_id = Attribute()
    ...     user_id = Attribute()
    ...     resource_key = Attribute(key_alias='password')
    >>> access = ResourceAccess(resource_id='1276', user_id='ymat', password='VXFPF93')
    >>> access.resource_key
    'VXFPF93'

Extra validation
................

You can add attribute dependent extra validator methods for attribute relations etc., using ``validation_hook`` decorator::

    >>> from froshki import Froshki, Attribute, validation_hook
    >>>
    >>> class SendInquiry(Froshki):
    ...     user_name = Attribute()
    ...     user_contact = Attribute()
    ...     user_contact_confirmation = Attribute()
    ...     message = Attribute()
    ...     @validation_hook
    ...     def confirm_email(self):
    ...         return self.user_contact == self.user_contact_confirmation
    >>>
    >>> send_inquiry = SendInquiry(
    ...     user_name='yu mat', user_contact='drowse314@gmail.com', user_contact_confirmation='drose@gmail.com',
    ...     message='cannot post messages to my group'
    ... )
    >>> send_inquiry.validate()
    False

If you need error information with these extra validators, extend the decorator as following::

    (...)
    >>> class SendInquiryExt(SendInquiry):
    ...     @validation_hook.extend(error='inconsistent email inputs')
    ...     def confirm_email(self):
    ...         return self.user_contact == self.user_contact_confirmation
    >>>
    >>> send_inquiry = SendInquiry(
    ...     user_name='yu mat', user_contact='drowse314@gmail.com', user_contact_confirmation='drose@gmail.com',
    ...     message='cannot post messages to my group'
    ... )
    >>> send_inquiry.validate()
    False
    >>> send_inquiry.errors
    {'confirm_email': 'inconsistent email inputs'}

Subclassing and attribute mixin
...............................

``froshki.Froshki`` subclasses are usable as base classes::

    (...)
    >>> class Resource(Froshki):
    ...     resource_id = ResourceId()
    >>>
    >>> class Download(Resource):
    ...     filetype = Filetype()
    >>>
    >>> download = Download(resource_id='9', filetype='pdf')
    >>> download.validate()
    True

Mixins are useful if you want to share some attribute definitions between schemas::

    (...)
    >>> class UserMixin(object):
    ...     user = Attribute()
    >>>
    >>> class DownloadAsUser(Download, UserMixin):
    ...     pass
    >>>
    >>> download_as_someone = DownloadAsUser(
    ...     resource_id='5', filetype='mobi',
    ...     user='ymat',
    ... )
    >>> download_as_someone.validate()
    True
    >>> download_as_someone.user
    'ymat'

You can use any classes as attribute mixins by attaching ``froshki.Attribute`` instances,
with the exception of ``froshki.Froshki`` subclass which causes MRO issue.

Other options
.............

``froshki.Froshki`` class has some useful options.

* ``Froshki.default_values``: provide attribute defaults as dict.
* ``Froshki.ignore_unkown_keys``: control if ``source`` argument accepts names that are not defined as attributes, or not (True/False).

Also some options for ``froshki.Attribute``.

* (As argument) ``Attribute(nullable=<bool>)``: allows ``None`` in validation (with any validation methods set).