z3c.saconfig


Namez3c.saconfig JSON
Version 1.0 PyPI version JSON
download
home_pagehttps://github.com/zopefoundation/z3c.saconfig/
SummaryMinimal SQLAlchemy ORM session configuration for Zope
upload_time2023-06-13 06:08:18
maintainer
docs_urlNone
authorMartijn Faassen
requires_python
licenseZPL 2.1
keywords zope relational sqlalchemy component integration
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            z3c.saconfig
************

Introduction
============

This aim of this package is to offer a simple but flexible way to
configure SQLAlchemy's scoped session support using the Zope component
architecture. This package is based on ``zope.sqlalchemy``, which
offers transaction integration between Zope and SQLAlchemy.

We sketch out two main scenarios here:

* one database per Zope instance.

* one database per site (or Grok application) in a Zope instance
  (and thus multiple databases per Zope instance).

GloballyScopedSession (one database per Zope instance)
======================================================

The simplest way to set up SQLAlchemy for Zope is to have a single
thread-scoped session that's global to your entire Zope
instance. Multiple applications will all share this session. The
engine is set up with a global utility.

We use the SQLAlchemy ``sqlalchemy.ext.declarative`` extension to
define some tables and classes::

  >>> from sqlalchemy import *
  >>> from sqlalchemy.orm import declarative_base
  >>> from sqlalchemy.orm import relationship

  >>> Base = declarative_base()
  >>> class User(Base):
  ...     __tablename__ = 'test_users'
  ...     id = Column('id', Integer, primary_key=True)
  ...     name = Column('name', String(50))
  ...     addresses = relationship("Address", backref="user")
  >>> class Address(Base):
  ...     __tablename__ = 'test_addresses'
  ...     id = Column('id', Integer, primary_key=True)
  ...     email = Column('email', String(50))
  ...     user_id = Column('user_id', Integer, ForeignKey('test_users.id'))

So far this doesn't differ from the ``zope.sqlalchemy`` example. We
now arrive at the first difference. Instead of making the engine
directly, we can set up the engine factory as a (global) utility. This
utility makes sure an engine is created and cached for us.

  >>> from z3c.saconfig import EngineFactory
  >>> engine_factory = EngineFactory(TEST_DSN)

You can pass the parameters you'd normally pass to
``sqlalchemy.create_engine`` to ``EngineFactory``.

We now register the engine factory as a global utility using
``zope.component``. Normally you'd use either ZCML or Grok to do this
confirmation, but we'll do it manually here::::

  >>> from zope import component
  >>> from z3c.saconfig.interfaces import IEngineFactory
  >>> component.provideUtility(engine_factory, provides=IEngineFactory)

Note that setting up an engine factory is not actually necessary in
the globally scoped use case. You could also just create the engine as
a global and pass it as ``bind`` when you create the
``GloballyScopedSession`` later.

Let's look up the engine by calling the factory and create the tables
in our test database::

  >>> engine = engine_factory()
  >>> Base.metadata.create_all(engine)

Now as for the second difference from ``zope.sqlalchemy``: how the
session is set up and used. We'll use the ``GloballyScopedSession``
utility to implement our session creation::

  >>> from z3c.saconfig import GloballyScopedSession

We give the constructor to ``GloballyScopedSession`` the parameters
you'd normally give to ``sqlalchemy.orm.create_session``, or
``sqlalchemy.orm.sessionmaker``::

  >>> utility = GloballyScopedSession(twophase=TEST_TWOPHASE)

``GlobalScopedSession`` looks up the engine using ``IEngineFactory``
if you don't supply your own ``bind``
argument. ``GloballyScopedSession`` also automatically sets up the
``autocommit``, ``autoflush`` and ``extension`` parameters to be the
right ones for Zope integration, so normally you wouldn't need to
supply these, but you could pass in your own if you do need it.

We now register this as an ``IScopedSession`` utility::

  >>> from z3c.saconfig.interfaces import IScopedSession
  >>> component.provideUtility(utility, provides=IScopedSession)

We are done with configuration now. As you have seen it involves
setting up two utilities, ``IEngineFactory`` and ``IScopedSession``,
where only the latter is really needed in this globally shared session
use case.

After the ``IScopedSession`` utility is registered, one can import the
``Session`` class from z3c.saconfig.  This ``Session`` class is like
the one you'd produce with ``sessionmaker`` from
SQLAlchemy. `z3c.saconfig.Session`` is intended to be the only
``Session`` class you'll ever need, as all configuration and Zope
integration is done automatically for you by ``z3c.saconfig``,
appropriate the context in Zope where you use it. There is no need to
create ``Session`` classes yourself with ``sessionmaker`` or
``scoped_sesion`` anymore.

We can now use the ``Session`` class to create a session which will
behave according to the utility we provided::

  >>> from z3c.saconfig import Session
  >>> session = Session()

Now things go the usual ``zope.sqlalchemy`` way, which is like
``SQLAlchemy`` except you can use Zope's ``transaction`` module::

  >>> session.query(User).all()
  []
  >>> import transaction
  >>> session.add(User(name='bob'))
  >>> transaction.commit()

  >>> session = Session()
  >>> bob = session.query(User).all()[0]
  >>> bob.name == 'bob'
  True
  >>> bob.addresses
  []

Events
======

When a new engine is created by an ``EngineFactory``, an
``IEngineCreatedEvent`` is fired. This event has an attribute
``engine`` that contains the engine that was just created::

  >>> from z3c.saconfig.interfaces import IEngineCreatedEvent
  >>> @component.adapter(IEngineCreatedEvent)
  ... def createdHandler(event):
  ...     print("created engine")
  ...     print("args: {0}".format(event.engine_args))
  ...     print("kw: {0}".format(event.engine_kw))
  >>> component.provideHandler(createdHandler)
  >>> event_engine_factory = EngineFactory(TEST_DSN1)
  >>> engine = event_engine_factory()
  created engine
  args: ('sqlite:///:memory:',)
  kw: {}

Let's get rid of the event handler again::

  >>> sm = component.getSiteManager()
  >>> sm.unregisterHandler(None,
  ...   required=[IEngineCreatedEvent])
  True

SiteScopedSession (one database per site)
=========================================

In the example above we have set up SQLAlchemy with Zope using
utilities, but it did not gain us very much, except that you can just
use ``zope.sqlalchemy.Session`` to get the correct session.

Now we'll see how we can set up different engines per site by
registering the engine factory as a local utility for each one.

In order to make this work, we'll set up ``SiteScopedSession`` instead
of ``GloballyScopedSession``. We need to subclass
``SiteScopedSession`` first because we need to implement its
``siteScopeFunc`` method, which should return a unique ID per site
(such as a path retrieved by ``zope.traversing.api.getPath``). We need
to implement it here, as ``z3c.saconfig`` leaves this policy up to the
application or a higher-level framework::

  >>> from z3c.saconfig import SiteScopedSession
  >>> class OurSiteScopedSession(SiteScopedSession):
  ...   def siteScopeFunc(self):
  ...      return getSite().id # the dummy site has a unique id
  >>> utility = OurSiteScopedSession()
  >>> component.provideUtility(utility, provides=IScopedSession)

We want to register two engine factories, each in a different site::

  >>> engine_factory1 = EngineFactory(TEST_DSN1)
  >>> engine_factory2 = EngineFactory(TEST_DSN2)

We need to set up the database in both new engines::

  >>> Base.metadata.create_all(engine_factory1())
  >>> Base.metadata.create_all(engine_factory2())

Let's now create two sites, each of which will be connected to another
engine::

  >>> site1 = DummySite(id=1)
  >>> site2 = DummySite(id=2)

We set the local engine factories for each site:

  >>> sm1 = site1.getSiteManager()
  >>> sm1.registerUtility(engine_factory1, provided=IEngineFactory)
  >>> sm2 = site2.getSiteManager()
  >>> sm2.registerUtility(engine_factory2, provided=IEngineFactory)

Just so we don't accidentally get it, we'll disable our global engine factory::

  >>> component.provideUtility(None, provides=IEngineFactory)

When we set the site to ``site1``, a lookup of ``IEngineFactory`` gets
us engine factory 1::

  >>> setSite(site1)
  >>> component.getUtility(IEngineFactory) is engine_factory1
  True

And when we set it to ``site2``, we'll get engine factory 2::

  >>> setSite(site2)
  >>> component.getUtility(IEngineFactory) is engine_factory2
  True

We can look up our global utility even if we're in a site::

  >>> component.getUtility(IScopedSession) is utility
  True

Phew. That was a lot of set up, but basically this is actually just
straightforward utility setup code; you should use the APIs or Grok's
``grok.local_utility`` directive to set up local utilities. Now all
that is out of the way, we can create a session for ``site1``::

  >>> setSite(site1)
  >>> session = Session()

The database is still empty::

  >>> session.query(User).all()
  []

We'll add something to this database now::

  >>> session.add(User(name='bob'))
  >>> transaction.commit()

``bob`` is now there::

  >>> session = Session()
  >>> session.query(User).all()[0].name == 'bob'
  True

Now we'll switch to ``site2``::

  >>> setSite(site2)

If we create a new session now, we should now be working with a
different database, which should still be empty::

  >>> session = Session()
  >>> session.query(User).all()
  []

We'll add ``fred`` to this database::

  >>> session.add(User(name='fred'))
  >>> transaction.commit()

Now ``fred`` is indeed there::

  >>> session = Session()
  >>> users = session.query(User).all()
  >>> len(users)
  1
  >>> users[0].name == 'fred'
  True

And ``bob`` is still in ``site1``::

  >>> setSite(site1)
  >>> session = Session()
  >>> users = session.query(User).all()
  >>> len(users)
  1
  >>> users[0].name == 'bob'
  True

Engines and Threading
=====================

  >>> engine = None
  >>> def setEngine():
  ...     global engine
  ...     engine = engine_factory1()

Engine factories must produce the same engine:

  >>> setEngine()
  >>> engine is engine_factory1()
  True

Even if you call it in a different thread:

  >>> import threading
  >>> engine = None
  >>> t = threading.Thread(target=setEngine)
  >>> t.start()
  >>> t.join()

  >>> engine is engine_factory1()
  True

Unless they are reset:

  >>> engine_factory1.reset()
  >>> engine is engine_factory1()
  False

Even engine factories with the same parameters created at (almost) the same
time should produce different engines:

  >>> EngineFactory(TEST_DSN1)() is EngineFactory(TEST_DSN1)()
  False

Configuration using ZCML
========================

A configuration directive is provided to register a database engine
factory using ZCML.

  >>> from io import BytesIO
  >>> from zope.configuration import xmlconfig
  >>> import z3c.saconfig
  >>> xmlconfig.XMLConfig('meta.zcml', z3c.saconfig)()

Let's try registering the directory again.

  >>> xmlconfig.xmlconfig(BytesIO(b"""
  ... <configure xmlns="http://namespaces.zope.org/db">
  ...   <engine name="dummy" url="sqlite:///:memory:" />
  ... </configure>"""))

  >>> component.getUtility(IEngineFactory, name="dummy")
  <z3c.saconfig.utility.EngineFactory object at ...>

This time with a setup call.

  >>> xmlconfig.xmlconfig(BytesIO(b"""
  ... <configure xmlns="http://namespaces.zope.org/db">
  ...   <engine name="dummy2" url="sqlite:///:memory:"
  ...           setup="z3c.saconfig.tests.engine_subscriber" />
  ... </configure>"""))
  got: Engine(sqlite:///:memory:)

It's also possible to specify connection pooling options:

  >>> xmlconfig.xmlconfig(BytesIO(b"""
  ... <configure xmlns="http://namespaces.zope.org/db">
  ...   <engine name="dummy" url="sqlite:///:memory:"
  ...       pool_size="1"
  ...       max_overflow="2"
  ...       pool_recycle="3"
  ...       pool_timeout="4"
  ...       />
  ... </configure>"""))

  >>> engineFactory = component.getUtility(IEngineFactory, name="dummy")
  >>> engineFactory._kw == {'echo': None, 'pool_size': 1, 'max_overflow': 2, 'pool_recycle': 3, 'pool_timeout': 4}
  True

(See the SQLAlchemy documentation on connection pooling for details on how
these arguments are used.)

The session directive is provided to register a scoped session utility:

  >>> xmlconfig.xmlconfig(BytesIO(b"""
  ... <configure xmlns="http://namespaces.zope.org/db">
  ...   <session name="dummy" engine="dummy2" />
  ... </configure>"""))

  >>> component.getUtility(IScopedSession, name="dummy")
  <z3c.saconfig.utility.GloballyScopedSession object at ...>

  >>> from z3c.saconfig import named_scoped_session
  >>> factory = component.getUtility(IEngineFactory, name="dummy2")
  >>> Session = named_scoped_session('dummy')
  >>> Session().bind is factory()
  True

z3c.saconfig
************

1.0 (2023-06-13)
================

- Add support for Python 3.9, 3.10, 3.11.

- Drop support for Python 2.7, 3.5, 3.6.

- Update tests to run with SQLAlchemy 2. (There are no guaranties that they
  still run with older versions.)

- Ignore ``convert_unicode`` parameter in ZCML ``engine`` directive, as it is
  no longer supported by SQLAlchemy 2.


0.16.0 (2020-04-03)
===================

- Added support for Python 3.7 [nazrulworld]
- Added support for Python 3.8 [icemac]
- Added support for zope.sqlalchemy >= 1.2 [cklinger]
- Updated local bootstrap.py [cklinger]
- Use newer SQLAlchemy for tests [cklinger]


0.15 (2018-11-30)
=================

- Added Python 3.5 and 3.6 compatibility [nazrulworld]
- fix: `Issue with python3 compatibility, on zope interface implementation <https://github.com/zopefoundation/z3c.saconfig/issues/4>`_ [nazrulworld]


0.14 (2015-06-29)
=================

- Drop support for sqlalchemy < 0.5
  [oggers]


0.13 (2011-07-26)
=================

- Register engine factory setup using a zcml action


0.12 (2010-09-28)
=================

- EngineCreatedEvent also gets ``engine_args`` and ``engine_kw`` as
  attributes, so that event handlers can potentially differentiate
  between engines.


0.11 (2010-07-05)
=================

- Add pool_size, max_overflow, pool_recycle and pool_timeout options to the
  <engine /> directive. This allows connection pooling options to be defined
  in ZCML.

- works with sqlalchemy >= 0.5 (wouldn't work with sqlalchemy > 5 prior)


0.10 (2010-01-18)
=================

- Support current ZTK code

- engine.echo must default to None for SQLAlchemy to honor
  logging.getLogger("sqlalchemy.engine").setLevel(...)

- Do not enable convert_unicode by default. This option changes
  standard SQLAlchemy behaviour by making String type columns return
  unicode data.  This can be especially painful in Zope2 environments
  where unicode is not always accepted.

- Add a convert_unicode option to the zcml engine statement, allowing
  people who need convert_unicode to enable it.


0.9.1 (2009-08-14)
==================

- Include documentation on PyPI.

- Small documentation tweaks.


0.9 (2009-08-14)
================

- Initial public release.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zopefoundation/z3c.saconfig/",
    "name": "z3c.saconfig",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "zope relational sqlalchemy component integration",
    "author": "Martijn Faassen",
    "author_email": "faassen@startifact.com",
    "download_url": "https://files.pythonhosted.org/packages/28/4c/fc3d6fc50bfd4b0d3fe665b8dd641acbf4b2d7ebabdbbc5f53cbe0ccddbd/z3c.saconfig-1.0.tar.gz",
    "platform": null,
    "description": "z3c.saconfig\n************\n\nIntroduction\n============\n\nThis aim of this package is to offer a simple but flexible way to\nconfigure SQLAlchemy's scoped session support using the Zope component\narchitecture. This package is based on ``zope.sqlalchemy``, which\noffers transaction integration between Zope and SQLAlchemy.\n\nWe sketch out two main scenarios here:\n\n* one database per Zope instance.\n\n* one database per site (or Grok application) in a Zope instance\n  (and thus multiple databases per Zope instance).\n\nGloballyScopedSession (one database per Zope instance)\n======================================================\n\nThe simplest way to set up SQLAlchemy for Zope is to have a single\nthread-scoped session that's global to your entire Zope\ninstance. Multiple applications will all share this session. The\nengine is set up with a global utility.\n\nWe use the SQLAlchemy ``sqlalchemy.ext.declarative`` extension to\ndefine some tables and classes::\n\n  >>> from sqlalchemy import *\n  >>> from sqlalchemy.orm import declarative_base\n  >>> from sqlalchemy.orm import relationship\n\n  >>> Base = declarative_base()\n  >>> class User(Base):\n  ...     __tablename__ = 'test_users'\n  ...     id = Column('id', Integer, primary_key=True)\n  ...     name = Column('name', String(50))\n  ...     addresses = relationship(\"Address\", backref=\"user\")\n  >>> class Address(Base):\n  ...     __tablename__ = 'test_addresses'\n  ...     id = Column('id', Integer, primary_key=True)\n  ...     email = Column('email', String(50))\n  ...     user_id = Column('user_id', Integer, ForeignKey('test_users.id'))\n\nSo far this doesn't differ from the ``zope.sqlalchemy`` example. We\nnow arrive at the first difference. Instead of making the engine\ndirectly, we can set up the engine factory as a (global) utility. This\nutility makes sure an engine is created and cached for us.\n\n  >>> from z3c.saconfig import EngineFactory\n  >>> engine_factory = EngineFactory(TEST_DSN)\n\nYou can pass the parameters you'd normally pass to\n``sqlalchemy.create_engine`` to ``EngineFactory``.\n\nWe now register the engine factory as a global utility using\n``zope.component``. Normally you'd use either ZCML or Grok to do this\nconfirmation, but we'll do it manually here::::\n\n  >>> from zope import component\n  >>> from z3c.saconfig.interfaces import IEngineFactory\n  >>> component.provideUtility(engine_factory, provides=IEngineFactory)\n\nNote that setting up an engine factory is not actually necessary in\nthe globally scoped use case. You could also just create the engine as\na global and pass it as ``bind`` when you create the\n``GloballyScopedSession`` later.\n\nLet's look up the engine by calling the factory and create the tables\nin our test database::\n\n  >>> engine = engine_factory()\n  >>> Base.metadata.create_all(engine)\n\nNow as for the second difference from ``zope.sqlalchemy``: how the\nsession is set up and used. We'll use the ``GloballyScopedSession``\nutility to implement our session creation::\n\n  >>> from z3c.saconfig import GloballyScopedSession\n\nWe give the constructor to ``GloballyScopedSession`` the parameters\nyou'd normally give to ``sqlalchemy.orm.create_session``, or\n``sqlalchemy.orm.sessionmaker``::\n\n  >>> utility = GloballyScopedSession(twophase=TEST_TWOPHASE)\n\n``GlobalScopedSession`` looks up the engine using ``IEngineFactory``\nif you don't supply your own ``bind``\nargument. ``GloballyScopedSession`` also automatically sets up the\n``autocommit``, ``autoflush`` and ``extension`` parameters to be the\nright ones for Zope integration, so normally you wouldn't need to\nsupply these, but you could pass in your own if you do need it.\n\nWe now register this as an ``IScopedSession`` utility::\n\n  >>> from z3c.saconfig.interfaces import IScopedSession\n  >>> component.provideUtility(utility, provides=IScopedSession)\n\nWe are done with configuration now. As you have seen it involves\nsetting up two utilities, ``IEngineFactory`` and ``IScopedSession``,\nwhere only the latter is really needed in this globally shared session\nuse case.\n\nAfter the ``IScopedSession`` utility is registered, one can import the\n``Session`` class from z3c.saconfig.  This ``Session`` class is like\nthe one you'd produce with ``sessionmaker`` from\nSQLAlchemy. `z3c.saconfig.Session`` is intended to be the only\n``Session`` class you'll ever need, as all configuration and Zope\nintegration is done automatically for you by ``z3c.saconfig``,\nappropriate the context in Zope where you use it. There is no need to\ncreate ``Session`` classes yourself with ``sessionmaker`` or\n``scoped_sesion`` anymore.\n\nWe can now use the ``Session`` class to create a session which will\nbehave according to the utility we provided::\n\n  >>> from z3c.saconfig import Session\n  >>> session = Session()\n\nNow things go the usual ``zope.sqlalchemy`` way, which is like\n``SQLAlchemy`` except you can use Zope's ``transaction`` module::\n\n  >>> session.query(User).all()\n  []\n  >>> import transaction\n  >>> session.add(User(name='bob'))\n  >>> transaction.commit()\n\n  >>> session = Session()\n  >>> bob = session.query(User).all()[0]\n  >>> bob.name == 'bob'\n  True\n  >>> bob.addresses\n  []\n\nEvents\n======\n\nWhen a new engine is created by an ``EngineFactory``, an\n``IEngineCreatedEvent`` is fired. This event has an attribute\n``engine`` that contains the engine that was just created::\n\n  >>> from z3c.saconfig.interfaces import IEngineCreatedEvent\n  >>> @component.adapter(IEngineCreatedEvent)\n  ... def createdHandler(event):\n  ...     print(\"created engine\")\n  ...     print(\"args: {0}\".format(event.engine_args))\n  ...     print(\"kw: {0}\".format(event.engine_kw))\n  >>> component.provideHandler(createdHandler)\n  >>> event_engine_factory = EngineFactory(TEST_DSN1)\n  >>> engine = event_engine_factory()\n  created engine\n  args: ('sqlite:///:memory:',)\n  kw: {}\n\nLet's get rid of the event handler again::\n\n  >>> sm = component.getSiteManager()\n  >>> sm.unregisterHandler(None,\n  ...   required=[IEngineCreatedEvent])\n  True\n\nSiteScopedSession (one database per site)\n=========================================\n\nIn the example above we have set up SQLAlchemy with Zope using\nutilities, but it did not gain us very much, except that you can just\nuse ``zope.sqlalchemy.Session`` to get the correct session.\n\nNow we'll see how we can set up different engines per site by\nregistering the engine factory as a local utility for each one.\n\nIn order to make this work, we'll set up ``SiteScopedSession`` instead\nof ``GloballyScopedSession``. We need to subclass\n``SiteScopedSession`` first because we need to implement its\n``siteScopeFunc`` method, which should return a unique ID per site\n(such as a path retrieved by ``zope.traversing.api.getPath``). We need\nto implement it here, as ``z3c.saconfig`` leaves this policy up to the\napplication or a higher-level framework::\n\n  >>> from z3c.saconfig import SiteScopedSession\n  >>> class OurSiteScopedSession(SiteScopedSession):\n  ...   def siteScopeFunc(self):\n  ...      return getSite().id # the dummy site has a unique id\n  >>> utility = OurSiteScopedSession()\n  >>> component.provideUtility(utility, provides=IScopedSession)\n\nWe want to register two engine factories, each in a different site::\n\n  >>> engine_factory1 = EngineFactory(TEST_DSN1)\n  >>> engine_factory2 = EngineFactory(TEST_DSN2)\n\nWe need to set up the database in both new engines::\n\n  >>> Base.metadata.create_all(engine_factory1())\n  >>> Base.metadata.create_all(engine_factory2())\n\nLet's now create two sites, each of which will be connected to another\nengine::\n\n  >>> site1 = DummySite(id=1)\n  >>> site2 = DummySite(id=2)\n\nWe set the local engine factories for each site:\n\n  >>> sm1 = site1.getSiteManager()\n  >>> sm1.registerUtility(engine_factory1, provided=IEngineFactory)\n  >>> sm2 = site2.getSiteManager()\n  >>> sm2.registerUtility(engine_factory2, provided=IEngineFactory)\n\nJust so we don't accidentally get it, we'll disable our global engine factory::\n\n  >>> component.provideUtility(None, provides=IEngineFactory)\n\nWhen we set the site to ``site1``, a lookup of ``IEngineFactory`` gets\nus engine factory 1::\n\n  >>> setSite(site1)\n  >>> component.getUtility(IEngineFactory) is engine_factory1\n  True\n\nAnd when we set it to ``site2``, we'll get engine factory 2::\n\n  >>> setSite(site2)\n  >>> component.getUtility(IEngineFactory) is engine_factory2\n  True\n\nWe can look up our global utility even if we're in a site::\n\n  >>> component.getUtility(IScopedSession) is utility\n  True\n\nPhew. That was a lot of set up, but basically this is actually just\nstraightforward utility setup code; you should use the APIs or Grok's\n``grok.local_utility`` directive to set up local utilities. Now all\nthat is out of the way, we can create a session for ``site1``::\n\n  >>> setSite(site1)\n  >>> session = Session()\n\nThe database is still empty::\n\n  >>> session.query(User).all()\n  []\n\nWe'll add something to this database now::\n\n  >>> session.add(User(name='bob'))\n  >>> transaction.commit()\n\n``bob`` is now there::\n\n  >>> session = Session()\n  >>> session.query(User).all()[0].name == 'bob'\n  True\n\nNow we'll switch to ``site2``::\n\n  >>> setSite(site2)\n\nIf we create a new session now, we should now be working with a\ndifferent database, which should still be empty::\n\n  >>> session = Session()\n  >>> session.query(User).all()\n  []\n\nWe'll add ``fred`` to this database::\n\n  >>> session.add(User(name='fred'))\n  >>> transaction.commit()\n\nNow ``fred`` is indeed there::\n\n  >>> session = Session()\n  >>> users = session.query(User).all()\n  >>> len(users)\n  1\n  >>> users[0].name == 'fred'\n  True\n\nAnd ``bob`` is still in ``site1``::\n\n  >>> setSite(site1)\n  >>> session = Session()\n  >>> users = session.query(User).all()\n  >>> len(users)\n  1\n  >>> users[0].name == 'bob'\n  True\n\nEngines and Threading\n=====================\n\n  >>> engine = None\n  >>> def setEngine():\n  ...     global engine\n  ...     engine = engine_factory1()\n\nEngine factories must produce the same engine:\n\n  >>> setEngine()\n  >>> engine is engine_factory1()\n  True\n\nEven if you call it in a different thread:\n\n  >>> import threading\n  >>> engine = None\n  >>> t = threading.Thread(target=setEngine)\n  >>> t.start()\n  >>> t.join()\n\n  >>> engine is engine_factory1()\n  True\n\nUnless they are reset:\n\n  >>> engine_factory1.reset()\n  >>> engine is engine_factory1()\n  False\n\nEven engine factories with the same parameters created at (almost) the same\ntime should produce different engines:\n\n  >>> EngineFactory(TEST_DSN1)() is EngineFactory(TEST_DSN1)()\n  False\n\nConfiguration using ZCML\n========================\n\nA configuration directive is provided to register a database engine\nfactory using ZCML.\n\n  >>> from io import BytesIO\n  >>> from zope.configuration import xmlconfig\n  >>> import z3c.saconfig\n  >>> xmlconfig.XMLConfig('meta.zcml', z3c.saconfig)()\n\nLet's try registering the directory again.\n\n  >>> xmlconfig.xmlconfig(BytesIO(b\"\"\"\n  ... <configure xmlns=\"http://namespaces.zope.org/db\">\n  ...   <engine name=\"dummy\" url=\"sqlite:///:memory:\" />\n  ... </configure>\"\"\"))\n\n  >>> component.getUtility(IEngineFactory, name=\"dummy\")\n  <z3c.saconfig.utility.EngineFactory object at ...>\n\nThis time with a setup call.\n\n  >>> xmlconfig.xmlconfig(BytesIO(b\"\"\"\n  ... <configure xmlns=\"http://namespaces.zope.org/db\">\n  ...   <engine name=\"dummy2\" url=\"sqlite:///:memory:\"\n  ...           setup=\"z3c.saconfig.tests.engine_subscriber\" />\n  ... </configure>\"\"\"))\n  got: Engine(sqlite:///:memory:)\n\nIt's also possible to specify connection pooling options:\n\n  >>> xmlconfig.xmlconfig(BytesIO(b\"\"\"\n  ... <configure xmlns=\"http://namespaces.zope.org/db\">\n  ...   <engine name=\"dummy\" url=\"sqlite:///:memory:\"\n  ...       pool_size=\"1\"\n  ...       max_overflow=\"2\"\n  ...       pool_recycle=\"3\"\n  ...       pool_timeout=\"4\"\n  ...       />\n  ... </configure>\"\"\"))\n\n  >>> engineFactory = component.getUtility(IEngineFactory, name=\"dummy\")\n  >>> engineFactory._kw == {'echo': None, 'pool_size': 1, 'max_overflow': 2, 'pool_recycle': 3, 'pool_timeout': 4}\n  True\n\n(See the SQLAlchemy documentation on connection pooling for details on how\nthese arguments are used.)\n\nThe session directive is provided to register a scoped session utility:\n\n  >>> xmlconfig.xmlconfig(BytesIO(b\"\"\"\n  ... <configure xmlns=\"http://namespaces.zope.org/db\">\n  ...   <session name=\"dummy\" engine=\"dummy2\" />\n  ... </configure>\"\"\"))\n\n  >>> component.getUtility(IScopedSession, name=\"dummy\")\n  <z3c.saconfig.utility.GloballyScopedSession object at ...>\n\n  >>> from z3c.saconfig import named_scoped_session\n  >>> factory = component.getUtility(IEngineFactory, name=\"dummy2\")\n  >>> Session = named_scoped_session('dummy')\n  >>> Session().bind is factory()\n  True\n\nz3c.saconfig\n************\n\n1.0 (2023-06-13)\n================\n\n- Add support for Python 3.9, 3.10, 3.11.\n\n- Drop support for Python 2.7, 3.5, 3.6.\n\n- Update tests to run with SQLAlchemy 2. (There are no guaranties that they\n  still run with older versions.)\n\n- Ignore ``convert_unicode`` parameter in ZCML ``engine`` directive, as it is\n  no longer supported by SQLAlchemy 2.\n\n\n0.16.0 (2020-04-03)\n===================\n\n- Added support for Python 3.7 [nazrulworld]\n- Added support for Python 3.8 [icemac]\n- Added support for zope.sqlalchemy >= 1.2 [cklinger]\n- Updated local bootstrap.py [cklinger]\n- Use newer SQLAlchemy for tests [cklinger]\n\n\n0.15 (2018-11-30)\n=================\n\n- Added Python 3.5 and 3.6 compatibility [nazrulworld]\n- fix: `Issue with python3 compatibility, on zope interface implementation <https://github.com/zopefoundation/z3c.saconfig/issues/4>`_ [nazrulworld]\n\n\n0.14 (2015-06-29)\n=================\n\n- Drop support for sqlalchemy < 0.5\n  [oggers]\n\n\n0.13 (2011-07-26)\n=================\n\n- Register engine factory setup using a zcml action\n\n\n0.12 (2010-09-28)\n=================\n\n- EngineCreatedEvent also gets ``engine_args`` and ``engine_kw`` as\n  attributes, so that event handlers can potentially differentiate\n  between engines.\n\n\n0.11 (2010-07-05)\n=================\n\n- Add pool_size, max_overflow, pool_recycle and pool_timeout options to the\n  <engine /> directive. This allows connection pooling options to be defined\n  in ZCML.\n\n- works with sqlalchemy >= 0.5 (wouldn't work with sqlalchemy > 5 prior)\n\n\n0.10 (2010-01-18)\n=================\n\n- Support current ZTK code\n\n- engine.echo must default to None for SQLAlchemy to honor\n  logging.getLogger(\"sqlalchemy.engine\").setLevel(...)\n\n- Do not enable convert_unicode by default. This option changes\n  standard SQLAlchemy behaviour by making String type columns return\n  unicode data.  This can be especially painful in Zope2 environments\n  where unicode is not always accepted.\n\n- Add a convert_unicode option to the zcml engine statement, allowing\n  people who need convert_unicode to enable it.\n\n\n0.9.1 (2009-08-14)\n==================\n\n- Include documentation on PyPI.\n\n- Small documentation tweaks.\n\n\n0.9 (2009-08-14)\n================\n\n- Initial public release.\n",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "Minimal SQLAlchemy ORM session configuration for Zope",
    "version": "1.0",
    "project_urls": {
        "Homepage": "https://github.com/zopefoundation/z3c.saconfig/"
    },
    "split_keywords": [
        "zope",
        "relational",
        "sqlalchemy",
        "component",
        "integration"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "527450989da77e20488c4ec41ef7b6d139aed6153210926cf752f951c14d2ff7",
                "md5": "883c440a94393f259f6cee686c3ee1c0",
                "sha256": "71bcf45aba2a5a94d6eda3698776e017de002cc0ad5df5e4659758864e180153"
            },
            "downloads": -1,
            "filename": "z3c.saconfig-1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "883c440a94393f259f6cee686c3ee1c0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 19927,
            "upload_time": "2023-06-13T06:08:16",
            "upload_time_iso_8601": "2023-06-13T06:08:16.934470Z",
            "url": "https://files.pythonhosted.org/packages/52/74/50989da77e20488c4ec41ef7b6d139aed6153210926cf752f951c14d2ff7/z3c.saconfig-1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "284cfc3d6fc50bfd4b0d3fe665b8dd641acbf4b2d7ebabdbbc5f53cbe0ccddbd",
                "md5": "62d838cdc6e8cb48099e40fe5b390610",
                "sha256": "89c6a2147fb46fed11aa3761e41a3d68a4401932b5323498f28797e8f62e2483"
            },
            "downloads": -1,
            "filename": "z3c.saconfig-1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "62d838cdc6e8cb48099e40fe5b390610",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 21275,
            "upload_time": "2023-06-13T06:08:18",
            "upload_time_iso_8601": "2023-06-13T06:08:18.602267Z",
            "url": "https://files.pythonhosted.org/packages/28/4c/fc3d6fc50bfd4b0d3fe665b8dd641acbf4b2d7ebabdbbc5f53cbe0ccddbd/z3c.saconfig-1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-13 06:08:18",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "zopefoundation",
    "github_project": "z3c.saconfig",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "z3c.saconfig"
}
        
Elapsed time: 0.18191s