Some rambling on the ORM-layer.
We have our own self-awareness framework (e.g. active-record-like
metadata-keeping - attributes, types, validation, ...), it's called
StaticType as it does just that - structures defined through it are
staticly-typed; no dynamic attributes, and no __dict__ at all. They
form the model, and everything after that is derived from/around
them - dialogs, db, etc. All is implemented with metaclasses /
descriptors.
So instead of reinventing the Obj-to-DB mapping wheel on some
primitive level, we decided to use SQL Alchemy.
Several problems arouse with this (from concrete to abstract):
- SA expects my objects to have __dict__, and uses it directly to
get/set attributes
- SA expects to be able to add any extra attributes to my objects, be
them system (e.g,. _sa_session_id) or not (foreign-key-columns)
- SA uses privately-named __sa_attr_state and while haveing properly
set _state avoids the whole need of it, even attempts to set it up in
a manager_attribute.init_attr() - just to save some AttributeError
later
- SA pushes db-semantics onto the objects, effectively making them
just __dict__s; any class X:pass works okay. I mean, the objects are
seen as extension to the database schema, and not the other way
around. Especialy with the persistency, where SA remembers history of
changes, and on unsuccessful transaction, that is, rollback (),
discards any values i have assigned to them and restores old values,
whatever they are.
i have invented a (slow!) solution/workaround for the first 3, but it
would be better if SA has more consistency in handling the objects -
e.g. need only ONE attribute and put any extra stuff into it, while
using proper getattr/setattr for the plain attributes.
For the last db-semantic stuff, i would like to be able to not have my
objects with exactly this db-persistency semantic pushed on them. So
if a object-saving transaction fails, okay, the object is still
dirty, let me retry later. Imagine that the data in the object needs
hours to be calculated.
Eventualy, i would like to have my own concept of dirti'ness.
Comparing things AND keeping history etc is not the best way sometimes.
Otherwise, the objects connected to the SA would have to be just
(smartly?) persistent copies of my own separate non-persistent
objects. Which sort-of makes the whole thing smelly...
Maybe if the dirtiness and related stuff can be separated from the
mapper in an (optional) layer/protocol, replaceable by external class
of same protocol, that would solve my problem.
Of course, it may have some unknwon-yet consequences, similar to, in a
push-vs-pull concepts, if there are two pushers attached against each
other, both trying to make the other one do something he cannot.
we will probably try do the above ourselves, copying data is not my
piece of cake, as i want each object to exist in ONE only instance.
so far all else is okay.
keep up the good work!
code-follows - for those who may have similar problems
================================
##svd'2006
if 'AVOID having privately-named __sa_attr_state':
from sqlalchemy.orm import mapperlib
mapperlib.attribute_manager.init_attr = lambda me: None #DO NOT
setup _sa_attr
class dict_via_attr( object):
'''this to avoid having __dict__ - SA uses __dict__ directly,
for both normal and for additionaly mapped attributes, e.g. ab_id;
use as __dict__ = property( dict_via_attr)
'''
__slots__ = [ 'src' ]
def __init__( me, src): me.src = src
def has_key( me, k):
try: me[k]
except KeyError: return False
return True
def __getitem__( me,k):
src = me.src
try: return getattr( src._props, k)
except AttributeError,e:
try: d = src._my_sa_stuff
except AttributeError: d = src._my_sa_stuff = dict()
#raise KeyError, a.args
return d[k]
def __setitem__( me, k,v):
src = me.src
try: return setattr( src._props, k,v)
except AttributeError,e:
try: d = src._my_sa_stuff
except AttributeError: d = src._my_sa_stuff = dict()
return d.__setitem__( k,v)
class Model4SA( Model): #Model is our base class
__slots__ = [ '__weakref__',
'_sa_session_id',
'_sa_insert_order',
'_instance_key',
'_entity_name',
'_my_state',
'_my_sa_stuff',
]
def _lazy_mystate( me):
try: return me._my_state
except AttributeError:
m = me._my_state = {}
return m
_state = property( _lazy_mystate)
__dict__ = property( dict_via_attr )
__doc__ = '''
SA uses obj.__dict__ directly - expects to add any stuff to it!
NO __dict__ here - all is done to avoid it, as it shades the
object and makes all the StaticType'ing meaningless.
SA-mapper-related attributes are hence split into these categories:
system: _state and others in the __slots__ above
extra columns (foreign keys etc): go in _my_sa_stuff
plain attributes: go in the object via get/set attr
this exercise would be a lot easier if:
- _state didn't use/make a privately-named __sa_attr_state
but just plain name (just _sa_attr_state)
(and the attribute_manager.init_attr() is redundant - one
func-call is ALOT slower than AttributeError exception)
- plain attributes didn't access directly obj.__dict__[k], but
have proper get/setattr(obj,k)
- extra columns and extra system attributes went all into the
above _state, or anywhere but in ONE place.
'''
#######.... subclass Model4SA instead of Model ...
#
#class B( Model4SA):
# name = _static_type.Text()
# next = _static_type.ForwardStruct('C')
...........
Well, trying to get SA to work with something like that is definitely
a square peg / round hole kind of thing. SA follows the Python
paradigm of not trying to restrict what you do unnecessarily.
(__slots__ is intended to be a performance optimization; using it to
restrict attribute creation is weird. Just use Java if that's what
you want.)
I would advise throwing away your existing framework and just using SA
the way it's meant to be used, as painful as that sounds.
--
Jonathan Ellis
http://spyced.blogspot.com