Skip to content

Instantly share code, notes, and snippets.

@BrianHicks
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BrianHicks/9070717 to your computer and use it in GitHub Desktop.
Save BrianHicks/9070717 to your computer and use it in GitHub Desktop.
"""\
TinyObj does the bare minimum to make a nice object layer on top of datastores
which return dicts. It can set defaults, validate data types, and provides a
dot-access style interface for getting and setting attributes (truthfully,
because it just sprinkles a little magic Python dust over the dicts so they are
full Python objects with all of those qualities.)
"""
import datetime
class Field(object):
"""base for other fields"""
def __init__(self):
self.default = None
def initialize(self, value=()):
"""\
initialize returns a cleaned value or the default, raising ValueErrors
as necessary.
"""
if value is ():
try:
return self.default()
except TypeError:
return self.default
else:
return self.clean(value)
def clean(self, value):
"""clean a value, returning the cleaned value"""
raise NotImplementedError
class UnicodeField(Field):
"""accept and validate unicode"""
def __init__(self):
self.default = unicode()
def clean(self, value):
return unicode(value)
class NumberField(Field):
"""accept and validate numbers"""
def __init__(self, t=float, allow_negative=True, allow_positive=True):
"""
take a type to coerce values to, can be (EG) ``float``, ``int``,
``long``, or ``complex``.
"""
self.t = t
self.default = t()
self.allow_negative = allow_negative
self.allow_positive = allow_positive
def clean(self, value):
if not isinstance(value, self.t):
value = self.t(value)
if not self.allow_negative and value < 0:
raise ValueError('value was negative')
if not self.allow_positive and value > 0:
raise ValueError('values was positive')
return value
class BoolField(Field):
"""accept and validate boolean numbers
note that this field will just call ``bool`` on values, this may not be
your desired behavior so you might want to implement a subclass that parses
truthy/falsey values in a way specific to your application"""
def __init__(self, default=False):
self.clean = bool
self.default = bool(default)
class NoValidationField(Field):
"""don't validate at all, but return the value passed defaulting to None"""
def initialize(self, value=None):
return value
class FieldParserMetaclass(type):
"""\
FieldParserMetaclass moves instances of ``Field`` to the ``_fields``
attribute on the class, preparing for actual values to be set in their
place.
"""
def __init__(self, name, parents, attrs):
fields = {}
for name, value in attrs.items():
if isinstance(value, Field):
fields[name] = value
self._fields = fields
class TinyObj(object):
"""\
TinyObj is the main superclass for objects which want to tie into the
getters/setters/deserialization/serialization mechanism. Subclass TinyObj
and provide a number of ``Field``s, like so::
class User(TinyObj):
username = UnicodeField()
password = UnicodeField()
active = BoolField(default=True)
def __unicode__(self):
return self.username
Then just use the fields like Python objects (because they are.)::
u = User(username='test', password='plaintext')
assert u.username == 'test'
assert u.password == 'plaintext'
assert u.active == False
"""
__metaclass__ = FieldParserMetaclass
def __init__(self, *doc, **attrs):
"""\
The expected usage pattern is to either pass a single dict or a number
of attributes. You can do both, the attributes will take precedence.
"""
source = {}
for item in doc:
source.update(doc)
source.update(attrs)
for key in set(source.keys()) | set(self._fields.keys()):
if key not in self._fields:
value = source[key]
elif key not in attrs:
value = self._fields[key].initialize()
else:
value = self._fields[key].initialize(source[key])
self.__dict__[key] = value
def __repr__(self):
if hasattr(self, '__unicode__'):
return u'<{}: {}>'.format(self.__class__.__name__, unicode(self))
else:
return u'<{}>'.format(self.__class__.__name__)
def to_dict(self):
"""return a dict of this object's fields"""
return self.__dict__
if __name__ == '__main__':
class User(TinyObj):
username = UnicodeField()
password = UnicodeField()
active = BoolField(default=True)
def __unicode__(self):
return self.username
# simple assertion tests
user = User(username='test', password='plaintext')
assert user.username == 'test'
assert user.password == 'plaintext'
assert user.active == True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment