Thanks -- the "before_compile" event seems interesting -- that could solve 
automatically adding a tenant on the SELECT side of things. What I wish for 
is a usage pattern like:

   1. When a web request comes in, determine the appropriate tenant
   2. Set the tenant somewhere associated with the context of the request
   3. Use the database as if it were single-tenant, relying on event 
   handlers/subclassed ORM objects/something to fill in the tenant for INSERT 
   and SELECT statements.

Does that seem realistic?

For schema-per-tenant, I can image how that would be done with 
schema_translate_map or search_path, since that covers both INSERT and 
SELECT. For row-per-tenant, is there something roughly equivalent to 
before_compile to be able to modify INSERT statements to include a tenant? 
There's the engine before_execute event -- maybe that would work -- but 
someone in this group has said those are evil 
<https://groups.google.com/forum/#!searchin/sqlalchemy/evil%7Csort:date/sqlalchemy/rtgKPoplH74/rELcSA1LBAAJ>
.

The "MultiAlchemy 
<https://www.google.com/url?q=https%3A%2F%2Fgithub.com%2Fmwhite%2FMultiAlchemy&sa=D&sntz=1&usg=AFQjCNHl7sPWv5DqsD1eX9cyjzWinhg04g>"
 
example I linked to earlier creates a Session subclass overriding the "add" 
method to automatically include the tenant, but it seems like that would 
miss objects that get added implicitly through relationships. 

Finally, a specific question about before_compile. If the query I want to 
modify is for first(), changing the query fails with:

sqlalchemy.exc.InvalidRequestError: Query.filter() being called on a Query 
which already has LIMIT or OFFSET applied. To modify the row-limited 
results of a  Query, call from_self() first.  Otherwise, call filter() 
before limit() or offset() are applied.

If I use from_self(), I get:

RecursionError: maximum recursion depth exceeded while calling a Python 
object


I guess because from_self causes the event handler to be fired again. I 
could use some kind of flag to avoid that, but needing to do so makes me 
wonder if I'm doing something wrong? Example:


from sqlalchemy import create_engine, Column, Integer, String, event, 
Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Query

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()
Session = sessionmaker(bind=engine)


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)
    deleted = Column(Boolean, default=False)


@event.listens_for(Query, "before_compile", retval=True)
def no_deleted(query):
    for desc in query.column_descriptions:
        if desc['type'] is User:
            entity = desc['entity']
            query = query.from_self().filter(entity.deleted == False)
    return query


Base.metadata.create_all(engine)
session = Session()
ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')
session.add(ed_user)
session.commit()

any_not_deleted_user = session.query(User).first()


Thanks again for the guidance,

Eric


-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to