[Python-modules-commits] r2832 - in /packages/sacontext: ./ branches/ branches/upstream/ branches/upstream/current/ branches/upstream/current/LICENSE branches/upstream/current/README branches/upstream/current/sacontext.py tags/
piotr at users.alioth.debian.org
piotr at users.alioth.debian.org
Thu Jul 26 10:26:23 UTC 2007
Author: piotr
Date: Thu Jul 26 10:26:23 2007
New Revision: 2832
URL: http://svn.debian.org/wsvn/python-modules/?sc=1&rev=2832
Log:
[svn-inject] Installing original source of sacontext
Added:
packages/sacontext/
packages/sacontext/branches/
packages/sacontext/branches/upstream/
packages/sacontext/branches/upstream/current/
packages/sacontext/branches/upstream/current/LICENSE
packages/sacontext/branches/upstream/current/README
packages/sacontext/branches/upstream/current/sacontext.py
packages/sacontext/tags/
Added: packages/sacontext/branches/upstream/current/LICENSE
URL: http://svn.debian.org/wsvn/python-modules/packages/sacontext/branches/upstream/current/LICENSE?rev=2832&op=file
==============================================================================
--- packages/sacontext/branches/upstream/current/LICENSE (added)
+++ packages/sacontext/branches/upstream/current/LICENSE Thu Jul 26 10:26:23 2007
@@ -1,0 +1,23 @@
+This is the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2007 by Mike Orr <sluggoster at gmail.com> and Michael Bayer
+<mike_mp at zzzcomputing.com>. Permission to copy & modify granted under the MIT
+license (http://opensource.org/licenses/mit-license.php).
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
Added: packages/sacontext/branches/upstream/current/README
URL: http://svn.debian.org/wsvn/python-modules/packages/sacontext/branches/upstream/current/README?rev=2832&op=file
==============================================================================
--- packages/sacontext/branches/upstream/current/README (added)
+++ packages/sacontext/branches/upstream/current/README Thu Jul 26 10:26:23 2007
@@ -1,0 +1,62 @@
+This Pylons application demonstrates the use of SAContext.
+
+For SAContext 0.3.2.
+
+(c) 2007 by Mike Orr <sluggoster at gmail.com>
+Permission to copy granted under the MIT license
+(http://opensource.org/licenses/mit-license.php)
+
+Installation and Use
+====================
+1. Prepare a workingenv (http://cheeseshop.python.org/pypi/workingenv.py) if
+ desired, otherwise make sure you have write permission to your Python
+ library directory (site-packages).
+
+2. Download http://sluggo.scrapping.cc/python/sacontext/SAC_Demo.tar.gz,
+ unpack it, and cd to the top-level directory.
+
+3. Run "python setup.py develop". This will download and install its
+ dependencies (Pylons and related packages, SQLAlchmey, Mako) if you don't
+ already have the right versions, and will put a "SAC_Demo.egg-link" file
+ in your Python library directory.
+
+4. The default configuration uses SQLite. You'll also need pysqlite if you're
+ using Python 2.4. If you wish to use a different SQL database supported by
+ SQLAlchemy, edit "the "sqlalchemy.default.uri" line in development.ini.
+
+5. Run "paster setup-app development.ini". This will create a
+ "flintstones.sqlite" database in the current directory (or whatever other
+ database you've chosen to use).
+
+6. Run "paster serve development.ini" and point your web browser to the URL
+ indicated (http://127.0.0.1:5000/). The home page is the entire demo.
+
+7. Press ctrl-c to stop the server when you get bored.
+
+
+Building the application
+========================
+
+Differences between this demo and the default Pylons application:
+
+- Development.ini contains these lines:
+
+ sqlalchemy.default.uri = sqlite:///$(here)s/flintstones.sqlite
+ sqlalchemy.default.echo = true
+ sqlalchemy.default.echo_pool = true
+ sqlalchemy.default.pool_recycle = 3600
+
+- sac_demo/models/__init__.py contains the 'sac' object, our tables, and our
+ mapped classes. Instead of pylons.database we use sac_demo.lib.sacontext,
+ which we've downloaded and put in the app's lib directory.
+
+- sac_demo/websetup.py contains instructions to create the database and
+ populate it with data.
+
+- sac_demo/config/middleware.py sets the default template engine to Mako.
+
+- sac_demo/config/routing.py sets a route for "" to our controller "main".
+ Delete the default home page, sac_demo/public/index.html.
+
+- sac_demo/controllers/main.py and sac_demo/templates/index.html were written
+ from scratch.
Added: packages/sacontext/branches/upstream/current/sacontext.py
URL: http://svn.debian.org/wsvn/python-modules/packages/sacontext/branches/upstream/current/sacontext.py?rev=2832&op=file
==============================================================================
--- packages/sacontext/branches/upstream/current/sacontext.py (added)
+++ packages/sacontext/branches/upstream/current/sacontext.py Thu Jul 26 10:26:23 2007
@@ -1,0 +1,739 @@
+"""A front end for SQLAlchemy applications with optional Pylons support.
+
+STATUS 2007/07/19:
+ - SAContext 0.3.3 is current version.
+ - Development has split into two branches:
+ * 0.3.x has a stable API and is compatible with SQLAlchemy 0.3.x. It will
+ NOT be forward compatible with SQLAlchemy 0.4.
+ * 0.4.x (unreleased) will be compatible with SQLAlchemy 0.4.x and 0.3.9 but
+ not with earlier versions.
+ - Compatible with Pylons 0.9.6 and late prereleases; not compatible with
+ Pylons 0.9.5.
+ - The add engine methods now return the engine and metadata they created as
+ a tuple, in case you want to keep external references to either or both.
+ - Elixir support contributed by beachcoder, who says it works with Tesla.
+ - All methods with argument 'key' accept "default", sacontext.DEFAULT,
+ and None interchangeably to refer to the default engine.
+ - Several backward-incompatible since 0.2.1; see the CHANGES list below.
+ - Passes test suite (test_sacontext.py) and Pylons demo (SAC_Demo).
+
+SAContext organizes your SQLAlchemy engines, metadatas, and sessions into one
+convenient object. That's *all* it does. SAContext helps new SQLAlchemists
+get their applications up and running quickly, while also scaling to large
+multi-database use cases.
+
+SAContext home page: http://sluggo.scrapping.cc/python/
+
+A test suite (test_sacontext.py) and Pylons demo (SAC_Demo) are available on
+the home page.
+
+Copyright (c) 2007 by Mike Orr <sluggoster at gmail.com> and Michael Bayer
+<mike_mp at zzzcomputing.com>. Permission to copy & modify granted under the MIT
+license (http://opensource.org/licenses/mit-license.php).
+
+Basic Concepts
+==============
+
+Here's the simplest one-database usage for normal (non-Pylons) applications::
+
+ 1 import sqlalchemy as sa
+ 2 from sqlalchemy.orm import mapper
+ 3 from sacontext import SAContext
+ 4 sac = SAContext()
+ 5 sac.add_engine(key=None, uri="sqlite://")
+ 6 users = sa.Table("Users", sac.metadata, sa.Column(...))
+ 7 class User(object): pass
+ 8 mapper(User, users)
+ 9 me = sac.query(User).get(123)
+ 10 me.age += 1
+ 11 sac.session.flush()
+
+Line 5 creates the default engine and its metadata. We know it's the default
+engine because the 'key' arg is None. (You can use None, sacontext.DEFAULT,
+or "default" interchangeably to refer to the default engine.) The URI
+indicates this is a SQLite memory database. We can access the default engine
+later via "sac.engine", and the default metadata via "sac.metadata". In fact,
+we use the default metadata in line 6 to define a table.
+
+"sac.session" in line 11 is a SQLAlchemy ORM session local to the current
+thread. It comes from a hidden SessionContext ("sac.session_context").
+"sac.query" in line 9 creates a SQLAlchemy Query; it's a shortcut for
+"sac.session.query", and equivalent to "session_context.current.query" in some
+other SQLAlchemy applications.
+
+Ah, but we can do more than this! Say you need to pass some engine options
+to SQLAlchemy.create_engine::
+
+ sac = SAContext()
+ sac.add_engine(key=None, uri="mysql://...", engine_options={"echo": True})
+
+Say you want to load a table schema from an existing database table::
+
+ table1 = sa.Table("Table1", sac.metadata, autoload=True)
+
+SAContext has built-in support for SQLAlchemy's SessionContext mapper
+extension::
+
+ mapper(User, users, extension=sac.ext)
+ me = User()
+ me.age = 24
+ sac.session.flush()
+
+Without the extension you'd have to do a separate "save" step::
+
+ mapper(User, users)
+ me = User()
+ me.age = 24
+ sac.session.save(me)
+ sac.session.flush()
+
+More information on the SessionContext extension is in the "Plugins" section of
+the SQLAlchemy manual.
+
+If you use the "assignmapper" extension you'll have to supply the session
+context directly::
+
+ assign_mapper(sac.session_context, User, users, properties={...}, ...)
+
+Pylons usage
+============
+
+PylonsSAContext does everything SAContext does but it also knows how to get
+engine data from the Pylons configuration. Put this in your model
+(myapp/models/__init__.py)::
+
+ from sacontext import PylonsSAContext
+ sac = PylonsSAContext()
+ sac.add_engine_from_config("default")
+
+This reads the URI from the Pylons 'app_conf' dict under the key
+"sqlalchemy.default.uri". As with .add_engine, you can use "default",
+sacontext.DEFAULT or None interchangeably to refer to the default engine.
+Other engine options may also be specified::
+
+ sqlalchemy.default.uri = mysql://username@localhost/mydb
+ sqlalchemy.default.echo = true
+ sqlalchemy.default.echo_pool = false
+ sqlalchemy.default.pool_recycle = 3600
+
+Options listed as boolean or integer in the SQLAlchemy manual are automatically
+coverted to the correct types. Other options are left as strings. There's no
+way to specify options that must be other types (e.g., 'poolclass' or
+'creator'). All options are passed directly to sqlalchemy.create_engine, which
+may raise an exception if it doesn't like an option.
+
+*Note:* the 'pool_recycle' option is important for MySQL, which unilaterally
+closes connections after an idle period. Using a dead connection causes a
+"MySQL server has gone away" errors in your application. The default timeout
+is 8 hours (configurable in my.cnf), so long-running applications like Pylons
+shoud set 'pool_recycle' much lower than that: 3600 seconds = 1 hour.
+
+.add_engine_from_config has several other arguments which we'll look at later.
+
+*Important:* Put this line at the beginning of your base controller's .__call__
+method (myapp/lib/base.py)::
+
+ model.sac.session.clear()
+
+This erases any stray session data left from the previous request in this
+thread. Otherwise you may get random errors or corrupt data. Or "del
+model.sac.session_context.current" if you prefer.
+
+Multiple static databases
+=========================
+
+Say your application stores its logging tables in a separate database::
+
+ sac = SAContext()
+ sac.add_engine(None, "mysql://.../myapp")
+ sac.add_engine("logs", "mysql://.../logs")
+ table1 = sa.Table("Table1", sac.get_metadata(None), sa.Column(...))
+ access_log = sa.Table("Access", sac.get_metadata("logs"), autoload=True)
+
+Here we have two engines, the default and "logs". Each table is bound to
+its appropriate database via its metadata. SQLAlchemy will remember which
+tables go with which database no matter what SQL or ORM queries you do, even
+if you use tables from multiple databases in the same session.
+
+This approach is very simple but it has one limitation: each table remains
+connected to the *same* database throughout the lifetime of the application.
+You *can* reconnect a metadata to a different engine but we don't recommend it;
+it destroys the parallelism of an engine-metadata pair under the same key.
+If the same table needs to access different databases at different times, use
+one of the dynamic approaches below, create your own metadata, pass
+an explicit engine to every SQL or ORM operation, or use the
+"table.tometadata(metadata2)" construct. There's no problem using
+SAContext's engines and sessions with your own metadatas.
+
+Multiple dynamic databases, one per session
+===========================================
+
+Say your application has the same tables in multiple databases, but uses
+exactly one database in each session. Think of a blog application with each
+blog in a separate database: every request connects to exactly one blog. For
+this you'll have to use a SAContext strategy called BoundSessionStrategy. In
+this case the metadatas are *unbound*; it's the *session* that's bound to an
+engine. ::
+
+ # At the beginning of the application.
+ from sacontext import BoundSessionStrategy
+ sac = SAContext(strategy=BoundSessionStrategy())
+ sac.add_engine("green", "mysql://username@localhost/green")
+ sac.add_engine("blue", "mysql://username@localhost/blue")
+ table1 = sa.Table("Table1", sac.get_metadata("green"), ...)
+
+ # At the beginning of the request. Say this request is for the "blue" blog.
+ # SQLAlchemy 0.4 uses sac.session.bind instead of sac.session.bind_to.
+ sac.session.bind_to = sac.get_engine("blue")
+
+Because the metadatas are unbound, it doesn't matter which one you use to
+define the tables with. The tradeoff is, you'll have to pass an explicit
+engine to any SQL construct that doesn't use the session::
+
+ table1.select(..., engine=sac.get_engine("green")).execute()
+ sac.get_engine("blue").execute("ALTER TABLE foo CHANGE foocol1 ...")
+
+You can use BoundSessionStrategy even if you *only* intend to do low-level SQL
+queries and *never* use the session. The strategy name is a misnomer in this
+case but it still works. You'll have to pass an engine to every method as
+above, or create your own metadata and call its .connect method. (The latter
+is not thread safe unless you're using ThreadLocalMetaData/DynamicMetaData.)
+
+This example does not have a default engine, so the "sac.engine" and
+"sac.metadata" properties are not available.
+
+Multiple dynamic databases, several per session
+===============================================
+
+This is a combination of the previous two scenarios. Say you need to bind
+different tables to different engines in the same session, but choose a
+different set of databases each session::
+
+ # At the beginning of the application.
+ sac = SAContext(BoundSessionStrategy())
+ sac.add_engine("dba1", "mysql://username@localhost/dba1")
+ sac.add_engine("dba2", "mysql://username@localhost/dba2")
+ sac.add_engine("dbb1", "mysql://username@localhost/dbb1")
+ sac.add_engine("dbb2", "mysql://username@localhost/dbb2")
+ table1 = sa.Table("Table1", sac.get_metadata("dba1"), ...)
+ table2 = sa.Table("Table1", sac.get_metadata("dba2"), ...)
+
+ # At the beginning of the request. Say this request is for the "b" series.
+ sac.session.bind_table(table1, "dbb1")
+ sac.session.bind_table(table2, "dbb2")
+
+Now the session can access both table1 and table2, and any changes will be
+written to database "b1" and "b2" respectively. There's also a .bind_mapper
+method that binds a mapper rather than a table. These methods affect only
+the current session, not your global table and mapper objects.
+
+Non-ORM SQL operations will still require an explicit engine argument, as in
+the previous scenario.
+
+More about PylonsSAContext
+==========================
+
+Now that we've discussed multiple engines, we see that
+PylonsSAContext.add_engine_from_config takes an argument 'key' that works
+exactly like .add_engine's 'key' argument. Passing None creates or replaces
+the default engine; passing a string creates or replaces a non-default engine.
+
+The prefix in the config file is assumed to be "sqlalchemy.default." for the
+default engine, or "sqlalchemy.the_key." for a non-default engine. You can
+explicitly set a different sub-prefix with the 'config_key' argument. ::
+
+ sac.add_engine_from_config(None, config_key="db1")
+ # 'sqlalchemy.db1.uri' -> default engine
+ sac.add_engine_from_config("green", config_key="verde")
+ # 'sqlalchemy.verde.uri' -> "green" engine
+
+The 'engine_options' and 'default_options' arguments are optional dicts
+containing additional options for sqlalchemy.create_engine. This is useful for
+non-scalar options that can't be specified in the config file; e.g., 'creator'.
+The keys should not have prefixes (e.g., "echo"). The values must be the
+correct types: no int/bool conversion is done. The difference between
+'engine_options' and 'default_options' is that the default options are used if
+the corresponding keys do not exist in the config file, whereas keys in
+'engine_options' override the config file.
+
+The 'uri' and 'default_uri' arguments work the same way: 'default_uri' is used
+if no URI was specified in the config file, and 'uri' overrides the config
+file's URI.
+
+'config' is an optional dict which, if specified, will be used as the
+configuration dict. Otherwise the method will ask Pylons or PasteDeploy for
+the configuration.
+
+Two support methods .parse_engine_options and .get_app_config are called by
+.add_engine_from_config but may be used standalone.
+
+PylonsSAContext overrides ._get_session_scope to provide a session scope
+suitable for Pylons. The scope spans the current Pylons application in the
+current thread. Two routines are considered the same application if they share
+the same pylons.g object.
+
+It is assumed that other SAContext subclasses for other frameworks will
+eventually be written.
+
+More about BoundSessionStrategy
+===============================
+
+BoundSessionStrategy was written by SQLAlchemy's author Mike Bayer, and I (Mike
+Orr) don't fully understand it. The constructor takes two arguments which
+don't seem useful to me but may be useful to advanced users. The argument are
+'connectionbound' and 'binds'.
+
+'binds' is a dict of tables/mappers to engine keys; it initializes
+every session to use those bindings. I'm not sure how useful it is given that
+you probably want to change the bindings depending on the request (otherwise
+you'd be using the default strategy that binds a table permanently to an
+engine), and many frameworks will have already created the session by the time
+you decide which tables to bind to. But it's here if you find it useful. You
+can try clearing the self.binds list and calling self.bind_table/mapper at
+every request; I'm not sure how well it will work.
+
+If 'connectionbound' is true, it binds each table/mapper to a specific
+connection rather than just to an engine. This may be useful for applications
+that want to keep a tight reign on which connection is used where. It works
+only with tables/mappers in the self.binds list, not with any you manually call
+sac.session.bind_table/mapper on. 'connectionbound' is not fully implemented;
+it doesn't share connections when it should.
+
+ElixirStrategy
+==============
+This is one way to use Elixir with SAContext. I don't use Elixir so I don't
+know if it's the best way or not. This class was contributed by beachcoder.
+Usage::
+
+ sac = SAContext(strategy=ElixirStrategy())
+
+You can also use this strategy with PylonsSAContext.
+
+
+
+CHANGELOG
+=========
+* 0.3.3 MO
+ - Fix spelling of Elixir throughout.
+
+* 0.3.2 MO
+ - Stable version for SQLAlchemy 0.3.x and Pylons 0.9.6. NOT forward
+ compatible with SQLAlchemy 0.4.
+ - .add_engine and .add_engine_from_config now returns the engine and metadata
+ it created as a tuple, in case you want to hold an external reference to
+ either or both. Requested by Andrey Petrov.
+ - New strategy ElixirStategy contributed by beachcoder. This code is
+ experimental.
+ - Bugfix in ._check_engine_key. Thanks to Karl Guterin for reporting it.
+
+* 0.3.1 MO
+ - All 'key' arguments recognize "default", sacontext.DEFAULT, and None
+ as aliases for the default engine. Internally it's keyed under "default".
+
+* 0.3.0 MO
+ - Several backward-incompatible changes.
+ - .__init__ takes only the 'strategy' arg and does not configure a
+ default engine. This means applications must call .add_engine or
+ .add_engine_from_config when previously they didn't.
+ - PylonsSAContext.add_engine no longer reads the Pylons configuration;
+ use new method .add_engine_from_config for this.
+ - .get_engine, .get_metadata, and .get_connectable require an argument.
+ - The default engine is now registered under the None key rather than
+ "default". When adding the default engine you must explicitly pass None to
+ .add_engine and .add_engine_from_config; the argument is required.
+ - Bugfixes in PylonsSAContext and BoundSessionStrategy thanks to Phillip
+ Jenvey and Avdd.
+
+* 0.2.1 MO
+ - Change imports for forward compatibility with SQLAlchemy 0.4.
+ - .session_context is now a public attribute.
+ - 'dburi' in a Pylons config file is now 'uri': sqlalchemy.default.uri.
+ - Fix variable names in .get_engine and .bind_table.
+ - Several of these are thanks to Waldemar Osuch's patch.
+
+* 0.2.0 MO
+ - Add 'config' argument to PylonsSAContext.__init__ & .add_engine.
+ - Fix variable name in PylonsSAContext.__init.
+
+* 0.1.0 (2007-06-??) MO
+ - Initial unstable release.
+"""
+
+import thread
+
+from sqlalchemy import create_engine, MetaData, Table
+from sqlalchemy.orm import create_session
+from sqlalchemy.ext.sessioncontext import SessionContext
+
+# Default engine/metadata.
+DEFAULT = "default"
+
+def asbool(obj):
+ """Borrowed from PasteDeploy-1.3/paste/deploy/converters.py
+ (c) 2005 by Ian Bicking, MIT license.
+ """
+ if isinstance(obj, (str, unicode)):
+ obj = obj.strip().lower()
+ if obj in ['true', 'yes', 'on', 'y', 't', '1']:
+ return True
+ elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
+ return False
+ else:
+ raise ValueError(
+ "String is not true/false: %r" % obj)
+ return bool(obj)
+
+def merge(*dicts):
+ """Merge several dicts into one. Key collisions are resolved in favor of
+ the dict on the left. `None` arguments are allowed and ignored.
+ """
+ ret = {}
+ for dic in reversed(dicts):
+ if dic:
+ ret.update(dic)
+ return ret
+
+
+#### SAContext class for any application ####
+class SAContext(object):
+ def __init__(self, strategy=None):
+ """Create an SAContext.
+
+ strategy: a strategy instance, or None for the default strategy.
+ Strategies currently available are BoundMetaDataStrategy
+ (default) and BoundSessionStrategy.
+ """
+ self._engines = {}
+ self._metadatas = {}
+ self._strategy = strategy or BoundMetaDataStrategy()
+ self.session_context = SessionContext(
+ lambda: self._strategy.create_session(self),
+ self._get_session_scope)
+
+ def add_engine(self, key, uri, engine_options=None):
+ """Add an engine and create a metadata for it. (The metadata will
+ be bound or unbound according to the strategy.)
+
+ key: string or None: An identifier for the engine and its metadata.
+ Pass None to add or replace the default engine. Otherwise
+ pass a short string name (e.g., "logs" or "western_region") to
+ add or replace a non-default engine.
+ uri: string: a SQLAlchemy database URI.
+ engine_options: dict: extra args for sqlalchemy.create_engine().
+
+ Returns a tuple: (engine, metadata). Normally you'd access these
+ as .engine, .metadata, .get_engine(key), .get_metadata(key), but
+ if you want to hold external references to them you can capture the
+ return value.
+ """
+ if key is None:
+ key = DEFAULT
+ if engine_options is None:
+ engine_options = {}
+ engine = create_engine(uri, **engine_options)
+ self._engines[key] = engine
+ metadata = self._strategy.create_metadata(key, self)
+ self._metadatas[key] = metadata
+ return engine, metadata
+
+ def get_connectable(self, key):
+ """Get an engine or connection appropriate for 'key', which was
+ previously passed to .add_engine.
+ """
+ if key is None:
+ key = DEFAULT
+ return self._strategy.get_connectable(key, self)
+
+ def get_engine(self, key):
+ """Get the engine previously created by .add_engine(key, ...)"""
+ if key is None:
+ key = DEFAULT
+ self._check_engine_key(self._engines, key)
+ return self._engines[key]
+
+ def get_metadata(self, key):
+ """Get the metadata previously created by .add_engine(key, ...)"""
+ if key is None:
+ key = DEFAULT
+ self._check_engine_key(self._metadatas, key)
+ return self._metadatas[key]
+
+ #### Properties
+ @property
+ def engine(self):
+ return self.get_engine(DEFAULT)
+
+ @property
+ def metadata(self):
+ return self.get_metadata(DEFAULT)
+
+ meta = metadata
+
+ @property
+ def connectable(self):
+ return self.get_connectable(DEFAULT)
+
+ @property
+ def ext(self):
+ return self.session_context.mapper_extension
+
+ @property
+ def session(self):
+ return self.session_context.current
+
+ @property
+ def query(self):
+ return self.session_context.current.query
+
+
+ #### Private methods
+ def _get_session_scope(self):
+ """Each session is local to the curent thread. This is identical to
+ sqlalchemy.SessionContext's default behavior.
+ """
+ return thread.get_ident
+
+
+ def _check_engine_key(self, engine_or_metadata, key):
+ """Raise KeyError if the key has not been registered."""
+ if key in engine_or_metadata:
+ return
+ if key is None:
+ raise KeyError(
+ "no default engine has been configured.\n"
+ "Call self.add_engine('default', ...)")
+ else:
+ raise KeyError("engine '%s' has not been configured.\n"
+ "Call self.add_engine." % key)
+
+
+#### PylonsSAContext class for Pylons applications ####
+class PylonsSAContext(SAContext):
+ """I'm a subclass of SAContext that can read engine options from a
+ Pylons config dict.
+ """
+
+ def add_engine_from_config(self, key, config_key=None, uri=None,
+ engine_options=None, default_uri=None, default_options=None,
+ config=None):
+ """Add an engine based on the 'config' dict or the current Pylons
+ configuration. See module docstring for arguments.
+
+ Returns a tuple: (engine, metadata). Normally you'd access these
+ as .engine, .metadata, .get_engine(key), .get_metadata(key), but
+ if you want to hold external references to them you can capture the
+ return value.
+ """
+ if key is None:
+ key = DEFAULT
+ if config is None:
+ config = self.get_app_config()
+ config_key = config_key or key or DEFAULT
+ parsed_uri, parsed_options = self.parse_engine_options(config,
+ config_key)
+ real_uri = uri or parsed_uri or default_uri
+ if not real_uri:
+ full_key = "sqlalchemy.%s.uri" % config_key
+ raise KeyError("no '%s' variable in config file" % full_key)
+ real_options = merge(engine_options, parsed_options, default_options)
+ return SAContext.add_engine(self, key, real_uri,
+ engine_options=real_options)
+
+
+ @staticmethod
+ def parse_engine_options(config, config_key="default"):
+ """Extract the database URI and engine options from a dict that's
+ equivalent to Pylons `app_config`. Convert int/bool options to
+ the appropriate types.
+
+ For example, say your Pylons .ini file looks like this:
+
+ [app_conf]
+ sqlalchemy.default.uri = sqlite:////tmp/mydb.sqlite
+ sqlalchemy.default.echo = false
+ sqlalchemy.default.pool_recycle = 3600
+ sqlalchemy.database2.uri = mysql://user:pw@example.com/mydb
+
+ Assume `config` is a dict corresponding to the above .ini file.
+ Calling `self.parse_engine_options(config)` returns::
+
+ {"uri": "sqlite:///tmp/mydb.sqlite",
+ "echo": False,
+ "pool_recycle": 3600}
+
+ Calling `self.parse_engine_options(config, "database2")` returns::
+
+ {"uri": "mysql://usr:pw/example.com/mydb"}
+
+ Calling `self.parse_engine_options(config, "MISSING")` returns::
+
+ {}
+
+ This is a static method so it can be called standalone.
+ """
+ prefix = "sqlalchemy.%s." % config_key
+ prefix_len = len(prefix)
+ uri = None
+ options = {}
+ for full_key in config.iterkeys():
+ if not full_key.startswith(prefix):
+ continue
+ value = config[full_key]
+ option = full_key[prefix_len:]
+ if option in BOOL_OPTIONS:
+ value = asbool(value)
+ elif option in INT_OPTIONS:
+ try:
+ value = int(value)
+ except ValueError:
+ reason = "config variable '%s' is non-numeric"
+ raise KeyError(reason % full_key)
+ if option == "uri":
+ uri = value
+ else:
+ options[option] = value
+ return uri, options
+
+
+ @staticmethod
+ def get_app_config():
+ """Get the Pylons 'app_conf' dict for the currently-running application.
+
+ This is a static method so it can be called standalone.
+ """
+ import pylons.config as config
+ if not hasattr(config, "__getitem__"): # Pylons 0.9.5
+ from paste.deploy import CONFIG as config
+ return config
+
+
+ #### Private methods
+ def _get_session_scope(self):
+ """Return the id keying the current database session's scope.
+
+ The session is particular to the current Pylons application -- this
+ returns an id generated from the current thread and the current Pylons
+ application's Globals object at pylons.g (if one is registered).
+
+ Copied from pylons.database in Pylons 0.9.5.
+ """
+ import pylons
+ try:
+ app_scope_id = str(id(pylons.g._current_obj()))
+ except TypeError:
+ app_scope_id = ''
+ return '%s|%i' % (app_scope_id, thread.get_ident())
+
+
+BOOL_OPTIONS = set([
+ "convert_unicode",
+ "echo",
+ "echo_pool",
+ "threaded",
+ "use_ansi",
+ "use_oids",
+ ])
+
+INT_OPTIONS = set([
+ "max_overflow",
+ "pool_size",
+ "pool_recycle",
+ "pool_timeout",
+ ])
+
+
+
+#### Private strategy classes ####
+class ContextualStrategy(object):
+ """Abstract base class."""
+
+ def create_session(self, context):
+ raise NotImplementedError("subclass responsibility")
+
+ def create_metadata(self, key, context):
+ raise NotImplementedError("subclass responsibility")
+
+ def get_connectable(self, key, context):
+ raise NotImplementedError("subclass responsibility")
+
+
+class BoundMetaDataStrategy(ContextualStrategy):
+ """A simple strategy that uses bound metadata. It's the SAContext
+ default, and recommended for most applications.
+ """
+ def create_session(self, context):
+ return create_session()
+
+ def create_metadata(self, key, context):
+ return MetaData(engine=context.get_engine(key))
+
+ def get_connectable(self, key, context):
+ return context.get_engine(key)
+
+
+class BoundSessionStrategy(ContextualStrategy):
+ """A strategy that allows the same table to be simultaneously bound to
+ one engine in one session and a different engine in another session.
+ If you don't need this, use BoundMetaDataStrategy instead.
+ """
+
+ def __init__(self, connectionbound=False, binds=None):
+ self.binds = []
+ if binds is not None:
+ for key in binds:
+ if isinstance(key, Table):
+ self.bind_table(key, binds[key])
+ else:
+ self.bind_mapper(key, binds[key])
+
+ self.connectionbound = connectionbound
+
+ def bind_mapper(self, mapper, engine_key):
+ self.binds.append(('bind_mapper', mapper, engine_key))
+
+ def bind_table(self, table, engine_key):
+ self.binds.append(('bind_table', table, engine_key))
+
+ def create_session(self, context):
+ if self.connectionbound:
+ bind_to = context.get_engine().connect()
+ else:
+ bind_to = context.get_engine()
+ session = create_session(bind_to=bind_to)
+
+ # set up mapper/table -specific binds
+ # TODO: in the case of "connectionbound",
+ # need to organize the connections here so that one
+ # connection per engine key
+ for bind_func, source, engine_key in self.binds:
+ bindto = context.get_engine(engine_key)
+ if self.connectionbound:
+ bindto = bindto.connect()
+ getattr(session, bind_func)(source, bindto)
+ return session
+
+ def create_metadata(self, key, context):
+ return MetaData()
+
+ def get_connectable(self, key, context):
+ # TODO: get "key" in here somehow, needs additional state stored
+ # in order to get correct "bind" from the Session
+ return context.session.bind_to
+
+
+class ElixirStrategy(ContextualStrategy):
+ """Contributed by beachcoder. Not officially supported.
+ Usage: sac = SAContext(strategy=ElixirStrategy())
+ """
+
+ def create_session(self, context):
+ import elixir
+ return elixir.objectstore.session
+
+ def create_metadata(self, key, context):
+ import elixir
+ elixir.metadata.connect(context.get_engine(key))
+ return elixir.metadata
+
+ def get_connectable(self, key, context):
+ return context.get_engine(key)
More information about the Python-modules-commits
mailing list