Created
February 15, 2011 23:38
-
-
Save santa4nt/828528 to your computer and use it in GitHub Desktop.
A module that defines metaclasses for slotted "info" data types.
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
| # -*- coding: utf-8 -*- | |
| """A module that defines metaclasses for slotted "info" data types. | |
| """ | |
| class InfoMeta(type): | |
| """A metaclass for classes that declaratively defines fields in | |
| advance. | |
| When a class with this metaclass is created, it should define a | |
| `_fields_` class attribute as a list of field names. During class | |
| creation these class attributes will be created with None values. | |
| """ | |
| def __new__(cls, name, bases, attrs): | |
| if '_fields_' in attrs: | |
| for field in attrs['_fields_']: | |
| attrs[field] = None | |
| else: | |
| raise TypeError('{0} is defined by metaclass InfoMeta ' | |
| 'but it does not define a class attribute ' | |
| '_field_.'.format(name)) | |
| super_new = super(InfoMeta, cls).__new__ | |
| extend_bases = bases | |
| return super_new(cls, name, extend_bases, attrs) | |
| class _StrictInfo(object): | |
| """Define inherited "magic methods" that enforce the strict rules | |
| described below. | |
| """ | |
| def __init__(self, **kwargs): | |
| for key, value in kwargs.items(): | |
| if not key in self.fields: | |
| raise AttributeError('_StrictInfo: {0} is not in the ' | |
| 'pre-defined list of fields.'.format(key)) | |
| setattr(self, key, value) | |
| @property | |
| def fields(self): | |
| return type(self)._fields_ | |
| def __setattr__(self, name, value): | |
| if name not in self.fields: | |
| raise AttributeError('_StrictInfo: {0} is not in the ' | |
| 'pre-defined list of fields.'.format(name)) | |
| super(_StrictInfo, self).__setattr__(name, value) | |
| def __repr__(self): | |
| import cStringIO as StringIO | |
| buildstr = StringIO.StringIO() | |
| buildstr.write(type(self).__name__ + '(') | |
| for attr in self.fields: | |
| value = getattr(self, attr) | |
| buildstr.write('{0}={1}, '.format(attr, repr(value))) | |
| buildstr.write(')') | |
| content = buildstr.getvalue() | |
| buildstr.close() | |
| return content | |
| def __str__(self): | |
| return repr(self) | |
| class StrictInfoMeta(type): | |
| """Similar to InfoMeta, but enforces attribute access to those | |
| that are defined in the class' _fields_ content only. | |
| This is done by forcing the configured class to inherit _StrictInfo. | |
| The latter defines the "magic methods" necessary to enforce this | |
| restriction. | |
| """ | |
| def __new__(cls, name, bases, attrs): | |
| if '_fields_' in attrs: | |
| for field in attrs['_fields_']: | |
| attrs[field] = None | |
| else: | |
| raise TypeError('{0} is defined by metaclass StrictInfoMeta ' | |
| 'but it does not define a class attribute ' | |
| '_field_.'.format(name)) | |
| super_new = super(StrictInfoMeta, cls).__new__ | |
| extend_bases = (_StrictInfo,) + bases # Enforce inheritance | |
| return super_new(cls, name, extend_bases, attrs) |
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 | |
| # -*- coding: utf-8 -*- | |
| import unittest | |
| import meta | |
| class TestMeta(unittest.TestCase): | |
| def test_InfoMeta(self): | |
| fields = ['foo', 'bar'] | |
| class Info(object): | |
| __metaclass__ = meta.InfoMeta | |
| _fields_ = fields | |
| f = Info() | |
| self.assertEqual(f.foo, None) | |
| self.assertEqual(f.bar, None) | |
| self.assertRaises(AttributeError, getattr, f, 'baz') | |
| self.assertEqual(f._fields_, fields) | |
| f.foo = 'Hello' | |
| self.assertEqual(f.foo, 'Hello') | |
| f.baz = 'World' | |
| self.assertEqual(f.baz, 'World') | |
| ## with self.assertRaises(TypeError): | |
| ## f = Info(foo='Hello', bar='World') | |
| self.assertRaises(TypeError, Info, foo='Hello', bar='World') | |
| def test_StrictInfoMeta(self): | |
| fields = ['foo', 'bar'] | |
| class StrictInfo(object): | |
| __metaclass__ = meta.StrictInfoMeta | |
| _fields_ = fields | |
| f = StrictInfo() | |
| self.assertEqual(f.foo, None) | |
| self.assertEqual(f.bar, None) | |
| self.assertRaises(AttributeError, getattr, f, 'baz') | |
| self.assertEqual(f.fields, fields) | |
| f.foo = 'Hello' | |
| self.assertEqual(f.foo, 'Hello') | |
| self.assertRaises(AttributeError, setattr, f, 'baz', 'World') | |
| self.assertRaises(AttributeError, StrictInfo, foo='Hello', bar='World', baz='!') | |
| if __name__ == '__main__': | |
| unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment