Created
March 21, 2012 12:24
-
-
Save j2labs/2146589 to your computer and use it in GitHub Desktop.
Basic Python Metaclasses with Configurable Options
This file contains 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 | |
import inspect | |
import copy | |
### | |
### Options Classes | |
### | |
class DocOptions(object): | |
"""This class is a container for all metaclass configuration options. The | |
`__init__` method will set the default values for attributes and then | |
attempt to map any keyword arguments to attributes of the same name. If an | |
attribute is passed as a keyword but the attribute is not given a default | |
value prior to called `_parse_kwargs_dict`, it will be ignored. | |
""" | |
def __init__(self, **kwargs): | |
self.store_structures = False | |
self.mixin = False | |
self.bucket = None | |
### Relies on attr names above | |
self._parse_kwargs_dict(kwargs) | |
def _parse_kwargs_dict(self, kwargs_dict): | |
"""This function receives the dictionary with keyword arguments in | |
`__init__` and maps them to existing attributes on the class. | |
""" | |
for k, v in kwargs_dict.items(): | |
if hasattr(self, k): | |
setattr(self, k, v) | |
class TopLevelDocOptions(DocOptions): | |
"""Extends `DocOptions` to add configuration values for | |
TopLevelDoc instances. | |
""" | |
def __init__(self, **kwargs): | |
self.id_field = None | |
self.auto_increment = None | |
### The call to super should be last | |
super(TopLevelDocOptions, self).__init__(**kwargs) | |
def _parse_meta_config(attrs, options_class): | |
"""Parses the Meta object on the class and translates it into an Option | |
instance. | |
""" | |
valid_attrs = dict() | |
if 'Meta' in attrs: | |
meta = attrs['Meta'] | |
for attr_name, attr_value in inspect.getmembers(meta): | |
if not attr_name.startswith('_'): | |
valid_attrs[attr_name] = attr_value | |
oc = options_class(**valid_attrs) | |
return oc | |
def _gen_options(klass, attrs): | |
"""Processes the attributes and class parameters to generate the correct | |
options structure. | |
Defaults to `DocOptions` but it's ideal to define `__optionsclass_` on the | |
Document's metaclass. | |
""" | |
### Parse Meta | |
options_class = DocOptions | |
if hasattr(klass, '__optionsclass__'): | |
options_class = klass.__optionsclass__ | |
options = _parse_meta_config(attrs, options_class) | |
return options | |
### | |
### Meta Classes | |
### | |
class DocMeta(type): | |
def __new__(cls, name, bases, attrs): | |
"""Processes a configuration of a Document type into a class. | |
""" | |
### Gen a class instance | |
klass = type.__new__(cls, name, bases, attrs) | |
### Parse metaclass config into options structure | |
options = _gen_options(klass, attrs) | |
klass._options = options | |
if hasattr(klass, 'Meta'): | |
delattr(klass, 'Meta') | |
### Generate class definition | |
return klass | |
class TopLevelDocMeta(DocMeta): | |
pass | |
### | |
### Base Documents | |
### | |
class BaseDoc(object): | |
__metaclass__ = DocMeta | |
__optionsclass__ = DocOptions | |
def __init__(self): | |
self._options = copy.copy(self._options) | |
class BaseTopLevelDoc(BaseDoc): | |
__metaclass__ = TopLevelDocMeta | |
__optionsclass__ = TopLevelDocOptions | |
class Meta: | |
mixin = True | |
id_field = 'some id field' | |
### | |
### Using the code | |
### | |
if __name__ == "__main__": | |
### Simple class with a single option that doesn't exist | |
print 'WORKING ON FOO ::' | |
class Foo(BaseDoc): | |
class Meta: | |
x = 'Foo' | |
f = Foo() | |
### Notice 'x' isn't listed | |
print 'F Options:', f._options.__dict__, '\n' | |
### Class with two options set | |
print 'WORKING ON BAR ::' | |
class Bar(BaseTopLevelDoc): | |
class Meta: | |
mixin = True | |
bucket = 'user_bars' | |
id_field = 'Word' | |
b = Bar() | |
print 'B Options:', b._options.__dict__, '\n' | |
### Configuration the options class at Class level | |
print 'WORKING ON BAZ ::' | |
class Baz(BaseTopLevelDoc): | |
__optionsclass__ = DocOptions # override default options cls | |
class Meta: | |
x = 'Baz' # unknown field | |
mixin = True | |
id_field = 'Word up' # unknown field | |
z = Baz() | |
zz = Baz() | |
print 'Z Options:', z._options.__dict__ | |
print 'Flipping a config setting on an instance\n' | |
zz._options.mixin = False | |
print 'ZZ Options:', zz._options.__dict__ | |
print 'Z Options:', z._options.__dict__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment