Created
May 23, 2012 07:54
-
-
Save j2labs/2773773 to your computer and use it in GitHub Desktop.
A path towards understanding DictShield.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
### | |
### Field Concepts | |
### | |
class IntField(object): | |
def __init__(self): | |
print 'new int field' | |
i = IntField() | |
print type(i) | |
i = 5 | |
print type(i) | |
### Field in a class | |
class Foo(object): | |
i = IntField() | |
x = 'xxxx' | |
foo = Foo() | |
foo.i = 'word' | |
### Use descriptors to implement getter and setters | |
class IntField(object): | |
def __get__(self, instance, owner): | |
print '__get__' | |
return self.value | |
def __set__(self, instance, value): | |
print '__set__' | |
self.value = value | |
class Foo(object): | |
i = IntField() | |
x = 'xxxx' | |
foo = Foo() | |
foo.i = 'word' | |
### Validation (almost!) | |
class IntField(object): | |
def __get__(self, instance, owner): | |
print '__get__' | |
return self.value | |
def __set__(self, instance, value): | |
print '__set__' | |
self.value = value | |
def validate(self): | |
try: | |
int(self.value) | |
except: | |
raise Exception() | |
class Foo(object): | |
i = IntField() | |
x = 'xxxx' | |
foo = Foo() | |
foo.i = 'word' | |
### Fails because foo.i returns 'word', which doesn't have a validate function | |
foo.i.validate() | |
foo.i = 4 | |
### Fails because foo.i returns 4, which doesn't have a validate function | |
foo.i.validate() | |
### Validation Via Indirection | |
class Foo(object): | |
i = IntField() | |
x = 'xxxx' | |
_fields = {'i': i} | |
foo = Foo() | |
f.i = 5 | |
f._fields['i'].validate() # Ugly. | |
class Foo(object): | |
i = IntField() | |
x = 'xxxx' | |
_fields = {'i': i} | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
field.validate() | |
foo = Foo() | |
foo.i = 5 | |
foo.validate() | |
### | |
### Metaprogramming ! | |
### | |
class MetaBar(type): | |
def __new__(cls, name, bases, attrs): | |
klass = type.__new__(cls, name, bases, attrs) | |
print 'Hello from MetaBar' | |
klass._some_field = 'some field' | |
return klass | |
class Bar(object): | |
__metaclass__ = MetaBar | |
def __init__(self): | |
print 'Hello from __init__' | |
### Using the Metaclass to populate _fields | |
class MetaBar(type): | |
def __new__(cls, name, bases, attrs): | |
klass = type.__new__(cls, name, bases, attrs) | |
klass._fields = dict() | |
for attr_name, attr_value in attrs.items(): | |
if type(attr_value) is IntField: | |
klass._fields[attr_name] = attr_value | |
return klass | |
class Foo(object): | |
__metaclass__ = MetaBar | |
i = IntField() | |
x = 'xxxx' | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
field.validate() | |
### | |
### Isolate Complex Logic | |
### | |
class BaseDocument(object): | |
__metaclass__ = MetaBar | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
field.validate() | |
### A user simply writes the class below | |
class Foo(BaseDocument): | |
i = IntField() | |
f = Foo() | |
f.i = 3 | |
f.validate() | |
### The current design stores value at class level. This is bad. | |
f1 = Foo() | |
f1.i = 3 | |
f2 = Foo() | |
f2.i = 234234 | |
print f1.i # It's 234234 isn't it? | |
### Instance-level storage via instance._data (aka self._data) | |
class IntField(object): | |
def __get__(self, instance, owner): | |
return instance._data.get('i', None) | |
def __set__(self, instance, value): | |
instance._data['i'] = value | |
def validate(self, instance, value): | |
try: | |
### Store coerced value | |
value = int(value) | |
instance._data['i'] = value | |
except: | |
raise Exception() | |
class MetaBar(type): | |
def __new__(cls, name, bases, attrs): | |
klass = type.__new__(cls, name, bases, attrs) | |
klass._fields = dict() | |
for attr_name, attr_value in attrs.items(): | |
if type(attr_value) is IntField: | |
klass._fields[attr_name] = attr_value | |
return klass | |
class BaseDocument(object): | |
__metaclass__ = MetaBar | |
def __init__(self, *a, **kw): | |
super(BaseDocument, self).__init__(*a, **kw) | |
self._data = dict() # This is how we have instance._data | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
value = self._data[field_name] | |
field.validate(self, value) | |
class Foo(BaseDocument): | |
i = IntField() | |
### The current design stores value at class level. This is bad. | |
f1 = Foo() | |
f1.i = 3 | |
f2 = Foo() | |
f2.i = 234234 | |
print f1.i # It's 3! | |
print f2.i # It's 234234. | |
### | |
### Instance Level Data | |
### | |
### First, break field logic into a `BaseField` | |
class BaseField(object): | |
def __get__(self, instance, owner): | |
"""Instance aware getter | |
""" | |
return instance._data.get('i', None) | |
def __set__(self, instance, value): | |
"""Instance aware setter | |
""" | |
instance._data['i'] = value | |
### Subclass the new `BaseField` | |
class IntField(BaseField): | |
"""Implementation of an int field. | |
""" | |
def validate(self, instance, value): | |
"""Calls `int()` on field for validation | |
""" | |
try: | |
### Store coerced value | |
value = int(value) | |
instance._data['i'] = value | |
except: | |
raise Exception() | |
### Adjust `MetaBar` to store any fields that are `BaseField` | |
class MetaBar(type): | |
def __new__(cls, name, bases, attrs): | |
"""Aggregates the `BaseField` instances into a dict called `_fields` | |
""" | |
klass = type.__new__(cls, name, bases, attrs) | |
klass._fields = dict() | |
for attr_name, attr_value in attrs.items(): | |
if isinstance(attr_value, BaseField): | |
klass._fields[attr_name] = attr_value | |
return klass | |
### Implement __init__ such that keyword arguments map to class fields | |
class BaseDocument(object): | |
"""A document that implements validation and provides a simple interface | |
for populating instance fields easily with a `dict`. | |
""" | |
__metaclass__ = MetaBar | |
def __init__(self, *a, **kw): | |
self._data = dict() | |
print 'KW:', kw | |
### Accept kw as values for class attributes | |
for field_name, field_value in kw.items(): | |
if field_name in self._fields: | |
field = self._fields[field_name] | |
if isinstance(field, BaseField): | |
self._data[field_name] = field_value | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
value = self._data[field_name] | |
field.validate(self, value) | |
### Use still has a simple interface | |
class Foo(BaseDocument): | |
"""Simple Foo Document that has a single IntField. | |
""" | |
i = IntField() | |
### Use new keyword helpers to populate fields | |
f1 = Foo(**{'i': 234}) | |
print f1.i | |
f2 = Foo(**{'i': 23423423423}) | |
print f2.i | |
### | |
### But wait, 'i' is hardcoded there... that sucks! | |
### | |
class BaseField(object): | |
def __init__(self): | |
self._field_name = None # We'll use self._field_name | |
def __get__(self, instance, owner): | |
return instance._data.get(self._field_name, None) | |
def __set__(self, instance, value): | |
instance._data[self._field_name] = value # self._field_name | |
### Subclass the new `BaseField` | |
class IntField(BaseField): | |
def validate(self, instance, value): | |
try: | |
### Store coerced value | |
value = int(value) | |
instance._data[self._field_name] = value # self._field_name | |
except: | |
raise Exception() | |
class MetaBar(type): | |
def __new__(cls, name, bases, attrs): | |
klass = type.__new__(cls, name, bases, attrs) | |
klass._fields = dict() | |
for attr_name, attr_value in attrs.items(): | |
if isinstance(attr_value, BaseField): | |
klass._fields[attr_name] = attr_value | |
attr_value._field_name = attr_name # self._field_name | |
return klass | |
class BaseDocument(object): | |
__metaclass__ = MetaBar | |
def __init__(self, *a, **kw): | |
self._data = dict() | |
for field_name, field_value in kw.items(): | |
if field_name in self._fields: | |
field = self._fields[field_name] | |
if isinstance(field, BaseField): | |
self._data[field_name] = field_value | |
field._field_name = field_name # self._field_name | |
def validate(self): | |
for field_name, field in self._fields.items(): | |
value = self._data[field_name] | |
field.validate(self, value) | |
### Use still has a simple interface | |
class Foo(BaseDocument): | |
i = IntField() | |
### Use new keyword helpers to populate fields | |
f1 = Foo(**{'i': 234}) | |
print f1.i | |
f2 = Foo(**{'i': 23423423423}) | |
print f2.i | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment