Michael Bayer wrote:
> ...
> 
> now.  should we just have session.connection() and have the mapper 
> argument be optional ?  I chose to make it required to start with, based 
> on "explicit is better than implicit".   since in most cases these days, 
> people have the engine accessible via the Tables inside of Mappers and 
> arent doing that explicit session thing, so in those cases 
> session.connection() wouldnt work.
> 

Yeah, that's a problem. I was thinking the dependencies should be more like 
this:


meta = MetaData(name="tables")

# associate metadata with engine on engine creation.
# metadata should be (is it?) a stateless object that
# can be associated with as many engines as needed,
# although in most cases it will only be with one
engine = create_engine(..., metadata=meta)

# obtaining a connection or transaction from an engine:
conn1 = engine.connect() # get a new connection (not in a transaction)
trans = engine.begin() # begin a transaction
conn2 = trans.connection() # get the underlying trans connection

# non-ORM selects
users1 = conn.execute(user_table.select(...))
users2 = conn.execute(user_mapper.select(...))
# or maybe like this? (I don't really have a preference)
users1 = user_table.select(...).execute(conn)
users2 = user_mapper.select(...).execute(conn)


# obtaining a connection or transaction from a session:
sess = Session(engine) # get a new session associated with engine
conn3 = sess.connect() # get a new connection (not in a transaction)
trans = sess.begin() # begin a new transaction for the session
conn4 = trans.connection() # get underlying trans connection
conn5 = sess.connect() # get a new connection (not in trans)

# ORM selects (nothing changed here...)
user = sess.query(User).get(...)
addresses = user.addresses # related select (implicit)


Please note, the names I used above are totally arbitrary and not necessarily 
suggestions to change the current API. I'm just trying to show how I think the 
API should logically work. Here's a more formal API definition:


class Engine(object):
    def connect(self, mapper_or_table=None):
        # returns a Connection outside of any
        # transaction context
    def begin(self):
        # begins and returns a Transaction

class Connection(object):
    def execute(self, sql_clause):
        # execute statement

class Transaction(object):
    def connection(self, mapper_or_table=None):
        # returns underlying connection
    def commit(self):
        # commit this transaction
    def rollback(self):
        # rollback this transaction

class Session(object):
    def connect(self, mapper_or_table=None):
        # returns a Connection outside of any
        # transaction context
    def begin(self):
        # begins and returns a Transaction
        # all subsequent session queries will
        # participate in this transaction
        # until its commit() or rollback()
        # method is called


I'm suggesting a consistent way to obtain connections and transactions. It's 
logical and easy to remember:

connections and transactions come from engines and sessions.

Implications:
1. tables and mappers, like metadata, are stateless objects that are not 
associated with any particular engine.
2. to execute table or mapper queries, the connection must be specified 
explicitly (or use a bound table or mapper--more on that below).
3. engines do not execute queries directly (use a connection to do that).
4. every session is associated with an engine.

This removes a lot of ambiguity in the current API. For example, when executing 
a query, do I use an engine or a connection? Both seem to support it, but what 
are the differences? Currently both the Connection and Engine have connect() 
and execute() methods. What does that mean? Are they both engines and 
connections at the same time? Why have two separate types if they both do the 
same things? PROPOSAL: If the engine only supports creating connections and the 
connection only supports executing queries, then its obvious. Of course the 
connection could have an engine property so it would be possible to get a new 
connection if all you had was a connection. On the other hand, maybe we could 
just eliminate Connection entirely and only have an engine. This actually might 
make more sense since it would seem to have a minimal impact on the API. An 
engine would be provided anywhere a connection is currently expected. That 
should "just work" since the connection and engine both suppor
t nearly the same API at the moment.

Another example is session.connect() and session.connection(). Even after 
studying the docstrings on each of these methods I wasn't sure how to answer 
the original question in this thread. A single connect() method with simple 
semantics makes it obvious. Requiring a session to be associated with an engine 
allows the session to act in a consistent way at all times.

The only real points of pain I see here are 1 and 2 (in Implications above). 
That's because there are many people who use tables and mappers that are bound 
to an engine. For backward compatibility, we could provide a BoundTable and 
BoundMapper, which would associate tables and mappers with an engine, and could 
execute queries directly. They could be implemented with a thread-local engine 
mechanism similar to SessionContext. This is really a separate "framework" that 
falls between stateless tables and the full-blown ORM. These Bound* classes 
would simply be wrappers for an EngineContext and a stateless table or mapper.

One corner case is a session with multiple engines. This could be handled by 
creating a CompositeEngine which would hide several engine instances behind a 
single engine interface. CompositeEngine.connect() (or execute() if we're 
ditching connections) would require a (normally optional) mapper or table 
instance to provide a way to resolve the correct engine (an exception would be 
raised if that parameter were not provided). The transaction.connection() and 
session.connect() methods would also have the same (normally optional) 
parameter. For the person using this setup, it is obvious that an additional 
parameter is needed to obtain a connection since it would be impossible to tell 
which engine was wanted without it. CompositeEngine.begin() would return a 
CompositeTransaction instance that would handle committing and/or rolling back 
transactions on all associated engines. As far as the session is concerned, it 
would treat the CompositeEngine and CompositeTransaction just like a
 normal engine or transaction--i.e. they would implement the same interface and 
therefore not need special treatment.

These are just a bunch of thoughts I've been dreaming up for a while. I'm not 
trying to start a flame war here, so please don't go ballistic on me when you 
start to think about how this could impact the current API. At this point this 
is all very hypothetical. Whatever solution (if any) we come up with, it will 
need to provide all of the current functionality of SA while hopefully cleaning 
up the API to make it much easier to comprehend for newcomers.

OK, I just realized that I need to rework this whole idea to reconcile the 
issues with the connection since it doesn't seem to be strictly necessary. Can 
anyone tell me why we need a Connection object if Engine has an execute() 
method? You may have noticed a few inconsistencies after you got to the 
paragraph on how engine and connection are currently very similar to the point 
of causing confusion. In this new idea that's forming in my head, an engine 
would have a connect() method that would return a plain DBAPI connection... 
I'll stop there because it's getting late and I need some sleep. I'll try to 
give more thoughts later this week. In the mean time I'd be happy to hear 
reactions. Sleep well... :)

~ Daniel


_______________________________________________
Sqlalchemy-users mailing list
Sqlalchemy-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users

Reply via email to