[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