Skip to content

Commit 7bb7e25

Browse files
committed
Added declarative and boundattributes, which will be the basis for
the new SQLObject metaclass git-svn-id: http://svn.colorstudy.com/trunk/SQLObject@572 95a46c32-92d2-0310-94a5-8d71aeb3d4b3
1 parent 6b49b85 commit 7bb7e25

4 files changed

Lines changed: 451 additions & 0 deletions

File tree

sqlobject/boundattributes.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Bound attributes are attributes that are bound to a specific class and
3+
a specific name. In SQLObject a typical example is a column object,
4+
which knows its name and class.
5+
6+
A bound attribute should define a method ``__addtoclass__(added_class,
7+
name)`` (attributes without this method will simply be treated as
8+
normal). The return value is ignored; if the attribute wishes to
9+
change the value in the class, it must call ``setattr(added_class,
10+
name, new_value)``.
11+
12+
BoundAttribute is a class that facilitates lazy attribute creation.
13+
14+
``bind_attributes(cls, new_attrs)`` is a function that looks for
15+
attributes with this special method. ``new_attrs`` is a dictionary,
16+
as typically passed into ``__classinit__`` with declarative (calling
17+
``bind_attributes`` in ``__classinit__`` would be typical).
18+
19+
Note if you do this that attributes defined in a superclass will not
20+
be rebound in subclasses. If you want to rebind attributes in
21+
subclasses, use ``bind_attributes_local``, which adds a
22+
``__bound_attributes__`` variable to your class to track these active
23+
attributes.
24+
"""
25+
26+
__all__ = ['BoundAttribute', 'BoundFactory', 'bind_attributes',
27+
'bind_attributes_local']
28+
29+
import declarative
30+
31+
class BoundAttribute(declarative.Declarative):
32+
33+
"""
34+
This is a declarative class that passes all the values given to it
35+
to another object. So you can pass it arguments (via
36+
__init__/__call__) or give it the equivalent of keyword arguments
37+
through subclassing. Then a bound object will be added in its
38+
place.
39+
40+
To hook this other object in, override ``make_object(added_class,
41+
name, **attrs)`` and maybe ``set_object(added_class, name,
42+
**attrs)`` (the default implementation of ``set_object``
43+
just resets the attribute to whatever ``make_object`` returned).
44+
"""
45+
46+
_private_variables = (
47+
'_private_variables',
48+
'_all_attributes',
49+
'__classinit__',
50+
'__addtoclass__',
51+
'_add_attrs',
52+
'set_object',
53+
'make_object',
54+
)
55+
56+
_all_attrs = ()
57+
58+
def __classinit__(cls, new_attrs):
59+
declarative.Declarative.__classinit__(cls, new_attrs)
60+
cls._all_attrs = cls._add_attrs(cls, new_attrs)
61+
62+
def __instanceinit__(self, new_attrs):
63+
declarative.Declarative.__instanceinit__(self, new_attrs)
64+
self._all_attrs = self._add_attrs(self, new_attrs)
65+
66+
def _add_attrs(this_object, new_attrs):
67+
private = this_object._private_variables
68+
all_attrs = list(this_object._all_attrs)
69+
for key in new_attrs.keys():
70+
if key.startswith('_') or key in private:
71+
continue
72+
if key not in all_attrs:
73+
all_attrs.append(key)
74+
return tuple(all_attrs)
75+
_add_attrs = staticmethod(_add_attrs)
76+
77+
def __addtoclass__(self, cls, added_class, attr_name):
78+
me = self or cls
79+
attrs = {}
80+
for name in me._all_attrs:
81+
attrs[name] = getattr(me, name)
82+
attrs['added_class'] = added_class
83+
attrs['attr_name'] = attr_name
84+
obj = me.make_object(**attrs)
85+
me.set_object(added_class, attr_name, obj)
86+
87+
__addtoclass__ = declarative.classinstancemethod(__addtoclass__)
88+
89+
def set_object(cls, added_class, attr_name, obj):
90+
setattr(added_class, attr_name, obj)
91+
92+
set_object = classmethod(set_object)
93+
94+
def make_object(cls, added_class, attr_name, *args, **attrs):
95+
raise NotImplementedError
96+
97+
make_object = classmethod(make_object)
98+
99+
class BoundFactory(BoundAttribute):
100+
101+
factory_class = None
102+
103+
def make_object(cls, *args, **kw):
104+
return cls.factory_class(*args, **kw)
105+
106+
def bind_attributes(cls, new_attrs):
107+
for name, value in new_attrs.items():
108+
if hasattr(value, '__addtoclass__'):
109+
value.__addtoclass__(cls, name)
110+
111+
def bind_attributes_local(cls, new_attrs):
112+
new_bound_attributes = {}
113+
for name, value in getattr(cls, '__bound_attributes__', {}).items():
114+
if new_attrs.has_key(name):
115+
# The attribute is being REbound, so don't try to bind it
116+
# again.
117+
continue
118+
value.__addtoclass__(cls, name)
119+
new_bound_attributes[name] = value
120+
for name, value in new_attrs.items():
121+
if hasattr(value, '__addtoclass__'):
122+
value.__addtoclass__(cls, name)
123+
new_bound_attributes[name] = value
124+
cls.__bound_attributes__ = new_bound_attributes

sqlobject/declarative.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Declarative objects.
3+
4+
Declarative objects have a simple protocol: you can use classes in
5+
lieu of instances and they are equivalent, and any keyword arguments
6+
you give to the constructor will override those instance variables.
7+
(So if a class is received, we'll simply instantiate an instance with
8+
no arguments).
9+
10+
You can provide a variable __unpackargs__ (a list of strings), and if
11+
the constructor is called with non-keyword arguments they will be
12+
interpreted as the given keyword arguments.
13+
14+
If __unpackargs__ is ('*', name), then all the arguments will be put
15+
in a variable by that name.
16+
17+
You can define a __classinit__(cls, new_attrs) method, which will be
18+
called when the class is created (including subclasses). Note: you
19+
can't use super() in __classinit__ because the class isn't bound to a
20+
name. As an analog to __classinit__, Declarative adds
21+
__instanceinit__ which is called with the same argument (new_attrs).
22+
This is like __init__, but after __unpackargs__ and other factors have
23+
been taken into account.
24+
25+
If __mutableattributes__ is defined as a sequence of strings, these
26+
attributes will not be shared between superclasses and their
27+
subclasses. E.g., if you have a class variable that contains a list
28+
and you append to that list, changes to subclasses will effect
29+
superclasses unless you add the attribute here.
30+
31+
Also defines classinstancemethod, which acts as either a class method
32+
or an instance method depending on where it is called.
33+
"""
34+
35+
from __future__ import generators
36+
37+
__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative')
38+
39+
import copy
40+
41+
try:
42+
import itertools
43+
counter = itertools.count()
44+
except ImportError:
45+
def _counter():
46+
i = 0
47+
while 1:
48+
i += 1
49+
yield i
50+
counter = _counter()
51+
52+
class classinstancemethod(object):
53+
"""
54+
Acts like a class method when called from a class, like an
55+
instance method when called by an instance. The method should
56+
take two arguments, 'self' and 'cls'; one of these will be None
57+
depending on how the method was called.
58+
"""
59+
60+
def __init__(self, func):
61+
self.func = func
62+
63+
def __get__(self, obj, type=None):
64+
return _methodwrapper(self.func, obj=obj, type=type)
65+
66+
class _methodwrapper(object):
67+
68+
def __init__(self, func, obj, type):
69+
self.func = func
70+
self.obj = obj
71+
self.type = type
72+
73+
def __call__(self, *args, **kw):
74+
assert not kw.has_key('self') and not kw.has_key('cls'), (
75+
"You cannot use 'self' or 'cls' arguments to a "
76+
"classinstancemethod")
77+
return self.func(*((self.obj, self.type) + args), **kw)
78+
79+
def __repr__(self):
80+
if self.obj is None:
81+
return ('<bound class method %s.%s>'
82+
% (self.type.__name__, self.func.func_name))
83+
else:
84+
return ('<bound method %s.%s of %r>'
85+
% (self.type.__name__, self.func.func_name, self.obj))
86+
87+
88+
class DeclarativeMeta(type):
89+
90+
def __new__(meta, class_name, bases, new_attrs):
91+
cls = type.__new__(meta, class_name, bases, new_attrs)
92+
if new_attrs.has_key('__classinit__'):
93+
cls.__classinit__ = staticmethod(cls.__classinit__.im_func)
94+
cls.declarative_count = counter.next()
95+
cls.__classinit__(cls, new_attrs)
96+
return cls
97+
98+
class Declarative(object):
99+
100+
__unpackargs__ = ()
101+
102+
__mutableattributes__ = ()
103+
104+
__metaclass__ = DeclarativeMeta
105+
106+
def __classinit__(cls, new_attrs):
107+
for name in cls.__mutableattributes__:
108+
if not new_attrs.has_key(name):
109+
setattr(cls, copy.copy(getattr(cls, name)))
110+
111+
def __instanceinit__(self, new_attrs):
112+
for name, value in new_attrs.items():
113+
setattr(self, name, value)
114+
if not new_attrs.has_key('declarative_count'):
115+
self.declarative_count = counter.next()
116+
117+
def __init__(self, *args, **kw):
118+
if self.__unpackargs__ and self.__unpackargs__[0] == '*':
119+
assert len(self.__unpackargs__) == 2, \
120+
"When using __unpackargs__ = ('*', varname), you must only provide a single variable name (you gave %r)" % self.__unpackargs__
121+
name = self.__unpackargs__[1]
122+
if kw.has_key(name):
123+
raise TypeError(
124+
"keyword parameter '%s' was given by position and name"
125+
% name)
126+
kw[name] = args
127+
else:
128+
if len(args) > len(self.__unpackargs__):
129+
raise TypeError(
130+
'%s() takes at most %i arguments (%i given)'
131+
% (self.__class__.__name__,
132+
len(self.__unpackargs__),
133+
len(args)))
134+
for name, arg in zip(self.__unpackargs__, args):
135+
if kw.has_key(name):
136+
raise TypeError(
137+
"keyword parameter '%s' was given by position and name"
138+
% name)
139+
kw[name] = arg
140+
if kw.has_key('__alsocopy'):
141+
for name, value in kw['__alsocopy'].items():
142+
if not kw.has_key(name):
143+
if name in self.__mutableattributes__:
144+
value = copy.copy(value)
145+
kw[name] = value
146+
del kw['__alsocopy']
147+
self.__instanceinit__(kw)
148+
149+
def __call__(self, *args, **kw):
150+
kw['__alsocopy'] = self.__dict__
151+
return self.__class__(*args, **kw)
152+
153+
def singleton(self, cls):
154+
if self:
155+
return self
156+
name = '_%s__singleton' % cls.__name__
157+
if not hasattr(cls, name):
158+
setattr(cls, name, cls(declarative_count=cls.declarative_count))
159+
return getattr(cls, name)
160+
singleton = classinstancemethod(singleton)
161+
162+
def __repr__(self, cls):
163+
if self:
164+
name = '%s object' % self.__class__.__name__
165+
v = self.__dict__.copy()
166+
else:
167+
name = '%s class' % cls.__name__
168+
v = cls.__dict__.copy()
169+
if v.has_key('declarative_count'):
170+
name = '%s %i' % (name, v['declarative_count'])
171+
del v['declarative_count']
172+
# @@: simplifying repr:
173+
#v = {}
174+
names = v.keys()
175+
args = []
176+
for n in self._repr_vars(names):
177+
args.append('%s=%r' % (n, v[n]))
178+
if not args:
179+
return '<%s>' % name
180+
else:
181+
return '<%s %s>' % (name, ' '.join(args))
182+
183+
def _repr_vars(dictNames):
184+
names = [n for n in dictNames
185+
if not n.startswith('_')
186+
and n != 'declarative_count']
187+
names.sort()
188+
return names
189+
_repr_vars = staticmethod(_repr_vars)
190+
191+
__repr__ = classinstancemethod(__repr__)
192+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from sqlobject import declarative
2+
from sqlobject import boundattributes
3+
4+
class TestMe(object):
5+
6+
__metaclass__ = declarative.DeclarativeMeta
7+
__classinit__ = boundattributes.bind_attributes_local
8+
9+
class AttrReplace(boundattributes.BoundAttribute):
10+
11+
__unpackargs__ = ('replace',)
12+
13+
replace = None
14+
15+
def make_object(self, cls, added_class, attr_name, **attrs):
16+
if not self:
17+
return cls.singleton().make_object(
18+
added_class, attr_name, **attrs)
19+
self.replace.added_class = added_class
20+
self.replace.name = attr_name
21+
assert attrs['replace'] is self.replace
22+
del attrs['replace']
23+
self.replace.attrs = attrs
24+
return self.replace
25+
26+
make_object = declarative.classinstancemethod(make_object)
27+
28+
class Holder:
29+
def __init__(self, name):
30+
self.holder_name = name
31+
def __repr__(self):
32+
return '<Holder %s>' % self.holder_name
33+
34+
def test_1():
35+
v1 = Holder('v1')
36+
v2 = Holder('v2')
37+
v3 = Holder('v3')
38+
class V2Class(AttrReplace):
39+
arg1 = 'nothing'
40+
arg2 = ['something']
41+
class A1(TestMe):
42+
a = AttrReplace(v1)
43+
v = V2Class(v2)
44+
class inline(AttrReplace):
45+
replace = v3
46+
arg3 = 'again'
47+
arg4 = 'so there'
48+
for n in ('a', 'v', 'inline'):
49+
assert getattr(A1, n).name == n
50+
assert getattr(A1, n).added_class is A1
51+
assert A1.a is v1
52+
assert A1.a.attrs == {}
53+
assert A1.v is v2
54+
assert A1.v.attrs == {'arg1': 'nothing', 'arg2': ['something']}
55+
assert A1.inline is v3
56+
assert A1.inline.attrs == {'arg3': 'again', 'arg4': 'so there'}
57+

0 commit comments

Comments
 (0)