i cant attach the file, so posting here at least as a reference.- i finally solved the whole problem and i also believe it is not overcomplicated. basically, i needed to have a composite object, access its attributes (which are another objects), and to keep the interface very simple to be able to send a simple str values. The values must be checked/converted (in my case, they must be unique, and created if not present). the solution is a combination of EntitySingleton example, association proxy, and some aching back
one question to sqlalchemy experts: is it possible to reduce the number of queries? i noticed that objects are reloaded every time insert/update is done even though in many cases the session already contains the data (and it would be ok just to check that insert/update operations on the db level succeeded)... from sqlalchemy import * from sqlalchemy.orm import mapper, sessionmaker, relation, backref, scoped_session from sqlalchemy.ext.associationproxy import association_proxy import weakref import operator import sys from sqlalchemy.ext.associationproxy import AssociationProxy Session = sessionmaker() #engine = create_engine('sqlite:///:memory:', echo=True) engine = create_engine('sqlite:///test.sqlite', echo=True) metadata = MetaData(engine) document_table = Table('document_table', metadata, Column('id', Integer(9), primary_key=True, autoincrement=True), Column('name', String(500)), Column('uri', String(500)), Column('processing_stage', String(500)), ) token_table = Table('token_table', metadata, Column('id', Integer(9), primary_key=True, autoincrement=True), Column('document_id', Integer, ForeignKey('document_table.id')), Column('pos', Integer(6)), Column('type', String(255)), #pozdeji to udelame sequence Column('surface_id', Integer, ForeignKey('surface_table.id')), Column('stripped_surface_id', Integer, ForeignKey('stripped_surface_table.id')), Column('sem_id', Integer, ForeignKey('sem_table.id')), ) surface_table = Table('surface_table', metadata, Column('id', Integer, primary_key=True, autoincrement=True), Column('value', String(500)) ) stripped_surface_table = Table('stripped_surface_table', metadata, Column('id', Integer, primary_key=True, autoincrement=True), Column('value', String(500)) ) sem_table = Table('sem_table', metadata, Column('id', Integer, primary_key=True, autoincrement=True), Column('value', String(500)) ) def myassociation_proxy(target_collection, attr, **kw): return MyAssociationProxy(target_collection, attr, **kw) class MyAssociationProxy(AssociationProxy): """ I need to change the behaviour of the AssociationProxy - to force it not to update attributes when they are replaced. Ie. obj.tokens = 'XXX' will keep the old token unchanged. I tried with getset_factory, but the problem is i cant replace the instance from the setter (or at least i dont know how to replace the self.target_collection with a new instance) The behaviour is changed only for scalars, for lists it remains the same as before """ def __set__(self, obj, values): if self.owning_class is None: self.owning_class = type(obj) if self.scalar is None: self.scalar = self._target_is_scalar() if self.scalar: self._initialize_scalar_accessors() if self.scalar: creator = self.creator and self.creator or self.target_class target = getattr(obj, self.target_collection) #make sure we add only the different instnces new_object = creator(values) if new_object is not getattr(obj, self.target_collection): setattr(obj, self.target_collection, new_object) #setattr(obj, self.target_collection, creator(values)) else: proxy = self.__get__(obj, None) if proxy is not values: proxy.clear() self._set(proxy, values) class EntitySingleton(type): """a metaclass that insures the creation of unique and non- existent entities for a particular constructor argument. if an entity with a particular constructor argument was already created, either in memory or in the database, it is returned in place of constructing the new instance.""" def __init__(cls, surface, bases, dct): cls.instances = weakref.WeakValueDictionary() def __call__(cls, value): session = Session() target_attribute = getattr(cls, getattr(cls, 'target_attribute')) hashkey = value try: return cls.instances[hashkey] except KeyError: instance = session.query(cls).filter(target_attribute==value).first() if instance is None: instance = type.__call__(cls, value) # optional - flush the instance when it's saved # session.flush([instance]) cls.instances[hashkey] = instance return instance class Document(object): def __init__(self, name): self.name = name class Token(object): # proxy the 'keyword' attribute from the 'kw' relation surface = myassociation_proxy('_surface', 'value') stripped_surface = myassociation_proxy('_stripped_surface', 'value') sem = myassociation_proxy('_sem', 'value') def __init__(self, pos=None, surface=None, stripped_surface=None, sem=None): if surface: self.surface = surface #str if stripped_surface: self.stripped_surface = stripped_surface if sem: self.sem = sem def __repr__(self): return "<%s(id:'%s',pos:'%s', surface:%s, stripped_surface:%s, sem:%s)>" % (self.__class__.__name__, self.id, self.pos, self._surface, self._stripped_surface, self._sem) class Surface(object): __metaclass__ = EntitySingleton target_attribute = 'value' def __init__(self, value): self.value = value def __repr__(self): return "<%s(id:'%s',value:'%s')>" % (self.__class__.__name__, self.id, getattr(self, self.target_attribute)) class StrippedSurface(object): __metaclass__ = EntitySingleton target_attribute = 'value' def __init__(self, value): self.value = value class Sem(Surface): __metaclass__ = EntitySingleton target_attribute = 'value' def __init__(self, value): self.value = value mapper(Document, document_table, properties ={ 'tokens':relation(Token, backref=backref('document')) }) mapper(Token, token_table, properties={ '_surface': relation(Surface, lazy=False), '_stripped_surface': relation(StrippedSurface, lazy=False), '_sem': relation(Sem, lazy=False), }) mapper(Surface, surface_table) mapper(StrippedSurface, stripped_surface_table) mapper(Sem, sem_table) metadata.drop_all() metadata.create_all() session = Session() doc = Document('jek') t = Token('XXX', surface='AAA') doc.tokens.append(t) t.surface = 'BBB' t.sem = 'xxx' session.add(doc) session.commit() t.surface = 'CCC' doc.tokens.append(Token(pos=1, surface='AAA', stripped_surface='ONE')) doc.tokens.append(Token(pos=2, surface='BBB', stripped_surface='ONE', sem = 'afgh')) doc.tokens.append(Token(pos=3, surface='CCC', stripped_surface='TWO', sem='afgh')) t = Token('XXX', surface='AAA') doc.tokens.append(t) t.surface = 'BBB' t.sem = 'eee' session.commit() On Oct 2, 4:59 pm, [EMAIL PROTECTED] wrote: > On Thursday 02 October 2008 17:13:24 rca wrote: > > > > > i think this should be it: > >http://www.sqlalchemy.org/docs/04/plugins.html#plugins_associationp > >roxy > > > and relevant discussion here: > >http://groups.google.com/group/sqlalchemy/browse_thread/thread/5256 > >5295f54fd85/31f53dca1e31e5ee?hl=en&lnk=gst&q=mapper+for+two+tables#3 > >1f53dca1e31e5ee > > > On Oct 2, 3:50 pm, rca <[EMAIL PROTECTED]> wrote: > > > Thanks again, it is much appreciated > > > > On Oct 2, 1:03 pm, [EMAIL PROTECTED] wrote: > > > > if the tables have nothing in common, u can have a > > > > MapperExtension that when touching one item updates the proper > > > > table. > > > > as a working big example, > > > > see sqlalachemyAggregator, either > > > > inhttp://dev.gafol.net/t/aggregator > > > > orhttp://dbcook.svn.sourceforge.net/viewvc/dbcook/trunk/dbcook/ > > > >misc/agg... which does things like counting summing etc > > > > runtime-updates-of-some-cache. > > > > but i'm not sure if that is what u want; imo u need it more > > > > explicit than this.. > > > > I have the same feeling (much less assertive here, me, the > > > sql/alchemy noop) > > > but i will study the example > > > > > and i still cant see how obj.surface=xyz will lookup and > > > > populate surface_id. imo u need 3 attributes: > > > > - _surface_text - table1.surface, hidden > > > > - _surface_id - table2.surface, hidden > > > > - surface: property which is visible and does the > > > > dispatch/lookup etc > > > > yes, the three attributes, i am able to do this now - what I am > > > unable is to map them to different tables -- and be persisted > > > when session.save(obj) -- and > > > session.query(obj).filter(obj.attribute_from_the_second_table) > > > It must be something stupid, i will try to come with an example > > hhmm depends if these 2 separate tables are also your objects or just > lookup tables. if they are objetcs, then _surf1 shoud point to that > Obj1/table1 object and change the attribute there; same for _surf2 / > Obj2/table2. > > if they're just lookup tables, then i dont know. how u link to which > record to update? give example. > > > > > ciao > > > > > On Thursday 02 October 2008 13:59:40 rca wrote: > > > > > thanks for the pointer, I tried and finally managed something > > > > > the synonym (with the big neon "don't panic" in my head) > > > > > > I can do the conversion and insert "integers id" in place of > > > > > "textual representation" and when accessing the parameter > > > > > .surface, get the textual representation instead of id > > > > > In this way, it works the same as my own type, but just more > > > > > flexible (it can access other parameters of the object). > > > > > > But still... is there a way to map one class to two tables in > > > > > one go? > > > > > > To have : > > > > > obj.surface_id --> table1.surface_id > > > > > obj.surface --> table2.surface > > > > > > when you do: > > > > > obj.surface = "XXX" > > > > > obj.name="YYY" > > > > > to have sqlachemy do: > > > > > "UPDATE table2 SET surface='XXX' WHERE table2.id = 2" (this > > > > > is a foreign key, the value is in the table1.surface_id) > > > > > *AND* > > > > > "UPDATE table1 SET name='YYY' WHERE table1.id = 8" > > > > > > i am getting this, which is wrong, because surface is foreign > > > > > key, an integer: > > > > > SELECT s1.* FROM table1 AS s1 WHERE s2.surface_id = 'XXX' > > > > > > if using the custom type, i am able to translate the > > > > > attribute to its id, and use it in the query, yet the synonym > > > > > might be better, only if it was possible to do > > > > > session.query(Object).filter(Object.surface== "XXX") > > > > > and get: SELECT s1.*, s2.* FROM table1 AS s1 LEFT JOIN table2 > > > > > AS s2 ON s1.surface_id = s2.id WHERE s2.surface = 'XXX' > > > > > > remember, my goal was to use obj.surface = "XXX" and have > > > > > this value referenced from the other table. > > > > > > Sorry if my questions are stupid! > > > > > > --roman > > > > > > On Oct 1, 10:21 am, [EMAIL PROTECTED] wrote: > > > > > > maybe try, instead of hiding the conversion inside db-types > > > > > > layer, to move it upper, i.e. make it a real > > > > > > reference/FK-column to real object/table, then make a > > > > > > python property (see "synonim") to do the conversion. > > > > > > > On Tuesday 30 September 2008 23:20:16 rca wrote: > > > > > > > Hi all, > > > > > > > > my apologies for a long question, I am new to sqlalchemy > > > > > > > and I was struggling with one problem. I solved it but I > > > > > > > really not sure it's the right way to do it. > > > > > > > > The problem is that I need to create objects like this: > > > > > > > Token(pos=2, surface="helloWorld") > > > > > > > > The surface is string, but in the database, it should be > > > > > > > saved as reference to an other table (looked-up and saved > > > > > > > if not existing) > > > > > > > > The way I solved it was to create my own column type > > > > > > > decorating Integer. On the way in, it gets text and > > > > > > > stores id, on the way out, it gives the text back. > > > > > > > > But I don't know if my solution is safe. For instance, > > > > > > > -- I am decorating Integer type, but on the output there > > > > > > > is string. -- I am using the EntitySingleton recipy, but > > > > > > > I found out I could not use the recommended > > > > > > > Session = scoped_session(sessionmaker()) > > > > > > > I had to say: Session = sessionmaker() > > > > > > > -- And I do commit for every new looked-up value > > > > > > > > And also, I have the impression the solution is very > > > > > > > complicated, perhaps you could help me to do it in a > > > > > > > better way. > > > > > > > > Many thanks! > > > > > > > > roman > > > > > > > > Here is the code with an example: > > > > > > > ------------------------------------------------------- > > > > > > > > import sqlalchemy as sa > > > > > > > from sqlalchemy.ext.declarative import declarative_base > > > > > > > from sqlalchemy.orm import scoped_session, sessionmaker, > > > > > > > relation, backref, mapper > > > > > > > from sqlalchemy import create_engine, Table, Column, > > > > > > > Integer, String, MetaData, ForeignKey > > > > > > > #from mimetypes import types_map > > > > > > > import weakref > > > > > > > import sqlalchemy.types as types > > > > > > > > Session = sessionmaker() #scoped_session(sessionmaker) > > > > > > > > DBase = declarative_base() > > > > > > > > class MyUnique(types.TypeDecorator): > > > > > > > """a type that decorates Integer, > > > > > > > receives a text representation - eg. > > > > > > > MyValueFromEnumeration and replaces it in the database > > > > > > > with id of the string above It makes sure that inserted > > > > > > > values are unique (creates them if not exist)""" > > > > > > > > impl = types.Integer > > > > > > > > def process_bind_param(self, value, dialect): > > > > > > > #ukladani do databaze > > > > > > > if not hasattr(self, '__unique_valuee'): > > > > > > > self.__unique_valuee = Surface(value) > > > > > > > return self.__unique_valuee.id > > > > > > > > def process_result_value(self, value, dialect): > > > > > > > #vystup > > > > > > > return self.__unique_valuee.surface > > > > > > > > """ #if it is string type > > > > > > > def copy(self): > > > > > > > return MyUniqueString(self.impl.length) > > > > > > > """ > > > > > > > > class DocumentBase(object): > > > > > > > def __init__(self): > > > > > > > self.metadata = DBase.metadata > > > > > > > self.engine = None > > > > > > > > def open(self, url='sqlite:///:memory:'): > > > > > > > self.engine = create_engine(url, echo=True) > > > > > > > #engine = create_engine('sqlite:///' + __file__ + > > > > > > > ".db", echo=True) > > > > > > > Session.configure(bind=self.engine) > > > > > > > self.metadata.bind = self.engine > > > > > > > > surface_table = sa.Table('surface', > > > > > > > self.metadata, sa.Column('id', sa.Integer, > > > > > > > primary_key=True, autoincrement=True), > > > > > > > sa.Column('surface', sa.String(500), > > > > > > > autoincrement=True) ) > > > > > > > > mapper(Surface, surface_table) > > > > > > > > self.metadata.create_all(checkfirst=True) > > > > > > > > def add(self, document_object): > > > > > > > sess = Session() > > > > > > > sess.save_or_update(document_object) > > > > > > > sess.commit() > > > > > > > > class EntitySingleton(type): > > > > > > > """a metaclass that insures the creation of unique > > > > > > > and non- existent entities for > > > > > > > a particular constructor argument. if an entity with > > > > > > > a particular constructor > > > > > > > argument was already created, either in memory or in > > > > > > > the database, it is returned > > > > > > > in place of constructing the new instance.""" > > > > > > > > def __init__(cls, surface, bases, dct): > > > > > > > cls.instances = weakref.WeakValueDictionary() > > > > > > > > def __call__(cls, surface): > > > > > > > session = Session() > > > > > > > > hashkey = surface > > > > > > > try: > > > > > > > return cls.instances[hashkey] > > > > > > > except KeyError: > > > > > > > instance = > > > > > > > session.query(cls).filter(cls.surface==surface).first() > > > > > > > if instance is None: > > > > > > > instance = > > ... > > read more » --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---