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