Skip to content

Commit

Permalink
limited support for sqlalchemy.orm.relationship
Browse files Browse the repository at this point in the history
  • Loading branch information
plq committed Sep 12, 2011
1 parent 2e4064d commit fcf95cf
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
7 changes: 3 additions & 4 deletions doc/source/tutorial/sqlalchemy.rst
Expand Up @@ -145,8 +145,8 @@ The SQLAlchemy integration is far from perfect at the moment:

* SQL constraints are not reflected to the interface document.
* It's not possible to define additional schema constraints.
* Object attributes defined by mechanisms other than Column are not directly
supported.
* Object attributes defined by mechanisms other than Column and a limited
form of `relationship` (no string arguments) are not supported.

If you need any of the above features, you need to separate the rpclib and
sqlalchemy object definitions.
Expand All @@ -158,8 +158,7 @@ Rpclib supports this with the following syntax: ::
__table__ = User.__table__

Here, The AlternativeUser object is automatically populated using columns from
the table definition. You should explicitly re-define attributes that are not
directly derivable from the table definition like the relationship()-based ones.
the table definition.

The context object is also a little bit different -- we start a transaction for
every call in the constructor of the UserDefinedContext object, and close it in
Expand Down
41 changes: 32 additions & 9 deletions src/rpclib/model/table.py
Expand Up @@ -33,7 +33,10 @@
logger = logging.getLogger(__name__)

import sqlalchemy

from warnings import warn
from sqlalchemy import Column
from sqlalchemy.orm import RelationshipProperty

from sqlalchemy.ext.declarative import DeclarativeMeta

Expand Down Expand Up @@ -65,16 +68,36 @@
def _process_item(v):
"""This function maps sqlalchemy types to rpclib types."""

if v.type in _type_map:
rpc_type = _type_map[v.type]
elif type(v.type) in _type_map:
rpc_type = _type_map[type(v.type)]
else:
raise Exception("soap_type was not found. maybe _type_map needs a new "
"entry. %r" % v)
rpc_type = None
if isinstance(v, Column):
if v.type in _type_map:
rpc_type = _type_map[v.type]
elif type(v.type) in _type_map:
rpc_type = _type_map[type(v.type)]
else:
raise Exception("soap_type was not found. maybe _type_map needs a new "
"entry. %r" % v)
elif isinstance(v, RelationshipProperty):
rpc_type = v.argument

return rpc_type

def _is_interesting(k, v):
if k.startswith('__'):
return False

if isinstance(v, Column):
return True

if isinstance(v, RelationshipProperty):
if getattr(v.argument,'_type_info', None) is None:
warn("the argument to relationship should be a reference to the real"
"column, not a string.")
return False

else:
return True

class TableSerializerMeta(DeclarativeMeta, ComplexModelMeta):
"""This class uses the information in class definition dictionary to build
the _type_info dictionary that rpclib relies on. It otherwise leaves
Expand All @@ -91,7 +114,7 @@ def __new__(cls, cls_name, cls_bases, cls_dict):
# mixin inheritance
for b in cls_bases:
for k,v in vars(b).items():
if isinstance(v, Column):
if _is_interesting(k,v):
_type_info[k] = _process_item(v)

# same table inheritance
Expand All @@ -110,7 +133,7 @@ def __new__(cls, cls_name, cls_bases, cls_dict):

# own attributes
for k, v in cls_dict.items():
if (not k.startswith('__')) and isinstance(v, Column):
if _is_interesting(k,v):
_type_info[k] = _process_item(v)

return DeclarativeMeta.__new__(cls, cls_name, cls_bases, cls_dict)
Expand Down
25 changes: 25 additions & 0 deletions src/rpclib/test/test_sqla.py
Expand Up @@ -281,5 +281,30 @@ class UserMail(User):
assert 'name' in UserMail._type_info
assert 'id' in UserMail._type_info

def test_relationship(self):
import sqlalchemy

class User(self.DeclarativeBase, TableSerializer):
__tablename__ = 'rpclib_user'

id = Column(sqlalchemy.Integer, primary_key=True)
name = Column(sqlalchemy.String(256))

class Address(self.DeclarativeBase, TableSerializer):
__tablename__ = 'rpclib_address'
id = Column(sqlalchemy.Integer, primary_key=True)
address = Column(sqlalchemy.String(256))
user_id = Column(sqlalchemy.Integer, ForeignKey(User.id), nullable=False)
user = relationship(User)

assert 'user' in Address._type_info
assert Address._type_info['user'] is User

u = User()
a = Address()
a.user = u



if __name__ == '__main__':
unittest.main()

0 comments on commit fcf95cf

Please sign in to comment.