[Sqlalchemy-commits] sqlalchemy: - Fixed uow bug whereby expired objects passed to
Brought to you by:
zzzeek
From: <co...@sq...> - 2010-12-15 21:31:57
|
details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/e7122c465d25 changeset: 7140:e7122c465d25 user: zzzeek date: Wed Dec 15 16:26:05 2010 -0500 description: - Fixed uow bug whereby expired objects passed to Session.delete() would not have unloaded references or collections taken into account when deleting objects, despite passive_deletes remaining at its default of False. [ticket:2002] Subject: sqlalchemy: - merge rbbd81cb9a341 from 0.6 branch details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/35c36502a4c7 changeset: 7141:35c36502a4c7 user: zzzeek date: Wed Dec 15 16:30:41 2010 -0500 description: - merge rbbd81cb9a341 from 0.6 branch diffstat: CHANGES | 6 + README.py3k | 14 +- README.unittests | 76 +- doc/build/core/engines.rst | 19 +- doc/build/core/event.rst | 104 + doc/build/core/events.rst | 30 + doc/build/core/index.rst | 4 +- doc/build/core/interfaces.rst | 17 +- doc/build/core/pooling.rst | 12 +- doc/build/core/schema.rst | 100 +- doc/build/core/tutorial.rst | 4 +- doc/build/dialects/sqlite.rst | 2 + doc/build/intro.rst | 8 +- doc/build/orm/events.rst | 49 + doc/build/orm/extensions/hybrid.rst | 12 + doc/build/orm/extensions/index.rst | 1 + doc/build/orm/index.rst | 3 +- doc/build/orm/interfaces.rst | 91 +- doc/build/orm/tutorial.rst | 32 +- examples/custom_attributes/custom_management.py | 3 +- examples/custom_attributes/listen_for_events.py | 50 +- examples/derived_attributes/__init__.py | 10 - examples/derived_attributes/attributes.py | 168 - examples/sharding/attribute_shard.py | 7 +- examples/versioning/test_versioning.py | 2 +- lib/sqlalchemy/__init__.py | 2 +- lib/sqlalchemy/connectors/mxodbc.py | 2 +- lib/sqlalchemy/dialects/firebird/base.py | 4 +- lib/sqlalchemy/dialects/maxdb/base.py | 1 + lib/sqlalchemy/dialects/mssql/base.py | 13 +- lib/sqlalchemy/dialects/mssql/pymssql.py | 1 - lib/sqlalchemy/dialects/mssql/pyodbc.py | 27 +- lib/sqlalchemy/dialects/mysql/base.py | 18 +- lib/sqlalchemy/dialects/oracle/cx_oracle.py | 12 +- lib/sqlalchemy/dialects/postgresql/base.py | 29 +- lib/sqlalchemy/dialects/postgresql/pg8000.py | 3 +- lib/sqlalchemy/dialects/postgresql/psycopg2.py | 3 +- lib/sqlalchemy/dialects/postgresql/pypostgresql.py | 1 - lib/sqlalchemy/dialects/sqlite/base.py | 28 +- lib/sqlalchemy/dialects/sqlite/pysqlite.py | 42 +- lib/sqlalchemy/dialects/sybase/base.py | 2 + lib/sqlalchemy/dialects/sybase/pyodbc.py | 2 +- lib/sqlalchemy/engine/base.py | 551 +- lib/sqlalchemy/engine/ddl.py | 38 +- lib/sqlalchemy/engine/default.py | 389 +- lib/sqlalchemy/engine/strategies.py | 27 +- lib/sqlalchemy/engine/threadlocal.py | 21 +- lib/sqlalchemy/event.py | 277 + lib/sqlalchemy/events.py | 319 + lib/sqlalchemy/ext/declarative.py | 24 +- lib/sqlalchemy/ext/horizontal_shard.py | 4 +- lib/sqlalchemy/ext/hybrid.py | 123 + lib/sqlalchemy/interfaces.py | 103 +- lib/sqlalchemy/log.py | 201 +- lib/sqlalchemy/orm/__init__.py | 99 +- lib/sqlalchemy/orm/attributes.py | 1124 +----- lib/sqlalchemy/orm/dependency.py | 89 +- lib/sqlalchemy/orm/deprecated_interfaces.py | 573 +++ lib/sqlalchemy/orm/dynamic.py | 25 +- lib/sqlalchemy/orm/events.py | 896 +++++ lib/sqlalchemy/orm/instrumentation.py | 683 ++++ lib/sqlalchemy/orm/interfaces.py | 495 +-- lib/sqlalchemy/orm/mapper.py | 898 ++-- lib/sqlalchemy/orm/properties.py | 248 +- lib/sqlalchemy/orm/query.py | 205 +- lib/sqlalchemy/orm/scoping.py | 86 +- lib/sqlalchemy/orm/session.py | 81 +- lib/sqlalchemy/orm/state.py | 85 +- lib/sqlalchemy/orm/strategies.py | 160 +- lib/sqlalchemy/orm/sync.py | 5 +- lib/sqlalchemy/orm/unitofwork.py | 58 +- lib/sqlalchemy/orm/util.py | 144 +- lib/sqlalchemy/pool.py | 292 +- lib/sqlalchemy/queue.py | 185 - lib/sqlalchemy/schema.py | 346 +- lib/sqlalchemy/sql/compiler.py | 37 +- lib/sqlalchemy/sql/expression.py | 265 +- lib/sqlalchemy/sql/operators.py | 2 +- lib/sqlalchemy/sql/util.py | 3 +- lib/sqlalchemy/test/__init__.py | 27 - lib/sqlalchemy/test/assertsql.py | 295 - lib/sqlalchemy/test/engines.py | 305 - lib/sqlalchemy/test/entities.py | 83 - lib/sqlalchemy/test/orm.py | 111 - lib/sqlalchemy/test/pickleable.py | 75 - lib/sqlalchemy/test/profiling.py | 222 - lib/sqlalchemy/test/requires.py | 327 - lib/sqlalchemy/test/schema.py | 79 - lib/sqlalchemy/test/testing.py | 797 ---- lib/sqlalchemy/test/util.py | 76 - lib/sqlalchemy/topological.py | 83 - lib/sqlalchemy/types.py | 240 +- lib/sqlalchemy/util.py | 1875 ----------- lib/sqlalchemy/util/__init__.py | 31 + lib/sqlalchemy/util/_collections.py | 885 +++++ lib/sqlalchemy/util/compat.py | 203 + lib/sqlalchemy/util/deprecations.py | 112 + lib/sqlalchemy/util/langhelpers.py | 691 ++++ lib/sqlalchemy/util/queue.py | 185 + lib/sqlalchemy/util/topological.py | 83 + lib/sqlalchemy_nose/config.py | 173 - lib/sqlalchemy_nose/noseplugin.py | 166 - setup.cfg | 2 +- setup.py | 13 +- sqla_nose.py | 18 +- test/aaa_profiling/test_compiler.py | 6 +- test/aaa_profiling/test_memusage.py | 14 +- test/aaa_profiling/test_orm.py | 111 +- test/aaa_profiling/test_pool.py | 10 +- test/aaa_profiling/test_resultset.py | 38 +- test/aaa_profiling/test_zoomark.py | 18 +- test/aaa_profiling/test_zoomark_orm.py | 14 +- test/base/test_dependency.py | 16 +- test/base/test_events.py | 264 + test/base/test_except.py | 2 +- test/base/test_utils.py | 8 +- test/bootstrap/config.py | 166 + test/bootstrap/noseplugin.py | 180 + test/dialect/test_access.py | 2 +- test/dialect/test_firebird.py | 4 +- test/dialect/test_informix.py | 2 +- test/dialect/test_maxdb.py | 8 +- test/dialect/test_mssql.py | 50 +- test/dialect/test_mxodbc.py | 4 +- test/dialect/test_mysql.py | 38 +- test/dialect/test_oracle.py | 50 +- test/dialect/test_postgresql.py | 69 +- test/dialect/test_sqlite.py | 17 +- test/dialect/test_sybase.py | 2 +- test/engine/_base.py | 4 +- test/engine/test_bind.py | 8 +- test/engine/test_ddlevents.py | 270 +- test/engine/test_execute.py | 430 ++- test/engine/test_metadata.py | 420 -- test/engine/test_parseconnect.py | 4 +- test/engine/test_pool.py | 368 +- test/engine/test_reconnect.py | 10 +- test/engine/test_reflection.py | 7 +- test/engine/test_transaction.py | 15 +- test/ex/test_examples.py | 2 +- test/ext/test_associationproxy.py | 6 +- test/ext/test_compiler.py | 2 +- test/ext/test_declarative.py | 226 +- test/ext/test_horizontal_shard.py | 9 +- test/ext/test_hybrid.py | 102 + test/ext/test_orderinglist.py | 4 +- test/ext/test_serializer.py | 16 +- test/ext/test_sqlsoup.py | 2 +- test/lib/__init__.py | 27 + test/lib/assertsql.py | 314 + test/lib/engines.py | 301 + test/lib/entities.py | 83 + test/lib/orm.py | 111 + test/lib/pickleable.py | 75 + test/lib/profiling.py | 234 + test/lib/requires.py | 327 + test/lib/schema.py | 79 + test/lib/testing.py | 795 ++++ test/lib/util.py | 125 + test/orm/_base.py | 9 +- test/orm/_fixtures.py | 6 +- test/orm/inheritance/test_abc_inheritance.py | 4 +- test/orm/inheritance/test_abc_polymorphic.py | 4 +- test/orm/inheritance/test_basic.py | 193 +- test/orm/inheritance/test_concrete.py | 12 +- test/orm/inheritance/test_magazine.py | 6 +- test/orm/inheritance/test_manytomany.py | 4 +- test/orm/inheritance/test_poly_linked_list.py | 8 +- test/orm/inheritance/test_polymorph.py | 75 +- test/orm/inheritance/test_polymorph2.py | 10 +- test/orm/inheritance/test_productspec.py | 4 +- test/orm/inheritance/test_query.py | 94 +- test/orm/inheritance/test_selects.py | 2 +- test/orm/inheritance/test_single.py | 6 +- test/orm/test_association.py | 6 +- test/orm/test_assorted_eager.py | 82 +- test/orm/test_attributes.py | 311 +- test/orm/test_backref_mutations.py | 13 +- test/orm/test_bind.py | 8 +- test/orm/test_cascade.py | 1255 +++++- test/orm/test_collection.py | 35 +- test/orm/test_compile.py | 37 +- test/orm/test_cycles.py | 8 +- test/orm/test_defaults.py | 18 +- test/orm/test_deprecations.py | 6 +- test/orm/test_dynamic.py | 9 +- test/orm/test_eager_relations.py | 78 +- test/orm/test_evaluator.py | 8 +- test/orm/test_expire.py | 10 +- test/orm/test_extendedattr.py | 46 +- test/orm/test_froms.py | 1593 +++++++++ test/orm/test_generative.py | 19 +- test/orm/test_immediate_load.py | 4 +- test/orm/test_instrumentation.py | 171 +- test/orm/test_joins.py | 1711 ++++++++++ test/orm/test_lazy_relations.py | 10 +- test/orm/test_load_on_fks.py | 6 +- test/orm/test_manytomany.py | 10 +- test/orm/test_mapper.py | 464 +- test/orm/test_merge.py | 12 +- test/orm/test_naturalpks.py | 10 +- test/orm/test_onetoone.py | 4 +- test/orm/test_pickled.py | 38 +- test/orm/test_query.py | 3325 +------------------ test/orm/test_relationships.py | 102 +- test/orm/test_scoping.py | 182 +- test/orm/test_selectable.py | 35 +- test/orm/test_session.py | 604 ++- test/orm/test_subquery_relations.py | 10 +- test/orm/test_transaction.py | 6 +- test/orm/test_unitofwork.py | 54 +- test/orm/test_unitofworkv2.py | 263 +- test/orm/test_utils.py | 46 +- test/orm/test_versioning.py | 6 +- test/perf/README | 17 + test/perf/insertspeed.py | 2 +- test/perf/large_flush.py | 2 +- test/perf/masscreate2.py | 2 +- test/perf/masseagerload.py | 2 +- test/perf/massload.py | 2 +- test/perf/masssave.py | 2 +- test/perf/objselectspeed.py | 4 +- test/perf/objupdatespeed.py | 4 +- test/perf/orm2010.py | 185 + test/perf/ormsession.py | 4 +- test/perf/poolload.py | 2 +- test/perf/sessions.py | 4 +- test/perf/stress_all.py | 2 +- test/perf/wsgi.py | 2 +- test/sql/test_case_statement.py | 4 +- test/sql/test_columns.py | 95 - test/sql/test_compiler.py | 39 +- test/sql/test_constraints.py | 10 +- test/sql/test_defaults.py | 8 +- test/sql/test_functions.py | 14 +- test/sql/test_generative.py | 19 +- test/sql/test_labels.py | 4 +- test/sql/test_metadata.py | 573 +++ test/sql/test_query.py | 27 +- test/sql/test_quote.py | 6 +- test/sql/test_returning.py | 6 +- test/sql/test_rowcount.py | 2 +- test/sql/test_selectable.py | 4 +- test/sql/test_types.py | 74 +- test/sql/test_unicode.py | 4 +- test/zblog/tables.py | 2 +- test/zblog/test_zblog.py | 2 +- 247 files changed, 20380 insertions(+), 14602 deletions(-) diffs (truncated from 44866 to 300 lines): diff -r ce4da51d644c -r 35c36502a4c7 CHANGES --- a/CHANGES Wed Dec 15 13:25:16 2010 -0500 +++ b/CHANGES Wed Dec 15 16:30:41 2010 -0500 @@ -14,6 +14,12 @@ that weren't previously saved in the "mutable changes" dictionary. + - Fixed uow bug whereby expired objects passed to + Session.delete() would not have unloaded references + or collections taken into account when deleting + objects, despite passive_deletes remaining at + its default of False. [ticket:2002] + - "innerjoin" flag doesn't take effect along the chain of joinedload() joins if a previous join in that chain is an outer join, thus allowing primary rows without diff -r ce4da51d644c -r 35c36502a4c7 README.py3k --- a/README.py3k Wed Dec 15 13:25:16 2010 -0500 +++ b/README.py3k Wed Dec 15 16:30:41 2010 -0500 @@ -39,17 +39,9 @@ Running Tests ------------- -To run the unit tests, ensure Distribute is installed as above, -and also that at least the ./lib/ and ./test/ directories have been converted -to Python 3 using the source tool above. A Python 3 version of Nose -can be acquired from Bitbucket using Mercurial: - - hg clone http://bitbucket.org/jpellerin/nose3/ - cd nose3 - python3 setup.py install - -The tests can then be run using the "nosetests3" script installed by the above, -using the same instructions in README.unittests. +To run unit tests in Py3k, Nose 1.0 is required, or a development +version of Nose that supports Python 3. The tests are run +using ./sqla_nose.py as described in README.unittests. Current 3k Issues ----------------- diff -r ce4da51d644c -r 35c36502a4c7 README.unittests --- a/README.unittests Wed Dec 15 13:25:16 2010 -0500 +++ b/README.unittests Wed Dec 15 16:30:41 2010 -0500 @@ -5,59 +5,37 @@ SQLAlchemy unit tests by default run using Python's built-in sqlite3 module. If running on Python 2.4, pysqlite must be installed. -Unit tests are run using nose. Note that in most cases, -nose needs to be installed manually. Documentation and -downloads for nose are available at: +Unit tests are run using nose. Nose is available at: -http://somethingaboutorange.com/mrl/projects/nose/0.11.1/index.html - -Or using setuptools: - - $ easy_install nose + http://pypi.python.org/pypi/nose/ SQLAlchemy implements a nose plugin that must be present when tests are run. -This plugin is available when SQLAlchemy is installed via setuptools. +This plugin is invoked when the test runner script provided with +SQLAlchemy is used. -INSTANT TEST RUNNER -------------------- +**NOTE:** - the nose plugin is no longer installed by setuptools as of +version 0.7 ! Use "python setup.py test" or "./sqla_nose.py". +RUNNING TESTS VIA SETUP.PY +-------------------------- A plain vanilla run of all tests using sqlite can be run via setup.py: $ python setup.py test - -(NOTE: this command is broken for Python 2.7 with nose 0.11.3, see -Nose issue 340. You will need to use 'nosetests' directly, see below.) - -Setuptools will take care of the rest ! To run nose directly and have -its full set of options available, read on... -SETUP ------ +The -v flag also works here: -All that's required is for SQLAlchemy to be installed via setuptools. -For example, to create a local install in a source distribution directory: + $ python setup.py test -v - $ export PYTHONPATH=. - $ python setup.py develop -d . - -The above will create a setuptools "development" distribution in the local -path, which allows the Nose plugin to be available when nosetests is run. -The plugin is enabled using the "with-sqlalchemy=True" configuration -in setup.cfg. - -RUNNING ALL TESTS ------------------ +RUNNING ALL TESTS +------------------ To run all tests: - $ nosetests - -(NOTE: if running with Python 2.7 and nose 0.11.3, add "-w test/" to the command. -Again this is a Nose issue, see Nose issue 342.) + $ ./sqla_nose.py If you're running the tests on Microsoft Windows, then there is an additional -argument that must be passed to nosetests: +argument that must be passed to ./sqla_nose.py: - > nosetests --first-package-wins=True + > ./sqla_nose.py --first-package-wins=True This is required because noseâs importer will normally evict a package from sys.modules if it sees a package with the same name in a different location. @@ -66,39 +44,29 @@ Assuming all tests pass, this is a very unexciting output. To make it more intersesting: - $ nosetests -v - -ALTERNATE TEST RUNNER ---------------------- - -The script "sqla_nose.py" is a front-end to Nose which manually associates -the SQLAlchemy testing plugin with Nose at runtime. This script can run the -tests without any reliance upon setuptools. In 0.7 we'll be removing the -Nose plugin from setup, so this will be the way going forward to run tests: - - $ python sqla_nose.py -v + $ ./sqla_nose.py -v RUNNING INDIVIDUAL TESTS ------------------------- Any directory of test modules can be run at once by specifying the directory path: - $ nosetest test/dialect + $ ./sqla_nose.py test/dialect Any test module can be run directly by specifying its module name: - $ nosetests test.orm.test_mapper + $ ./sqla_nose.py test.orm.test_mapper To run a specific test within the module, specify it as module:ClassName.methodname: - $ nosetests test.orm.test_mapper:MapperTest.test_utils + $ ./sqla_nose.py test.orm.test_mapper:MapperTest.test_utils COMMAND LINE OPTIONS -------------------- Help is available via --help: - $ nosetests --help + $ ./sqla_nose.py --help The --help screen is a combination of common nose options and options which the SQLAlchemy nose plugin adds. The most commonly SQLAlchemy-specific @@ -186,7 +154,7 @@ If you'll be running the tests frequently, database aliases can save a lot of typing. The --dbs option lists the built-in aliases and their matching URLs: - $ nosetests --dbs + $ ./sqla_nose.py --dbs Available --db options (use --dburi to override) mysql mysql://scott:tiger@127.0.0.1:3306/test oracle oracle://scott:tiger@127.0.0.1:1521 @@ -195,7 +163,7 @@ To run tests against an aliased database: - $ nosetests --db=postgresql + $ ./sqla_nose.py --db=postgresql To customize the URLs with your own users or hostnames, make a simple .ini file called `test.cfg` at the top level of the SQLAlchemy source distribution @@ -213,7 +181,7 @@ Any log target can be directed to the console with command line options, such as: - $ nosetests test.orm.unitofwork --log-info=sqlalchemy.orm.mapper \ + $ ./sqla_nose.py test.orm.unitofwork --log-info=sqlalchemy.orm.mapper \ --log-debug=sqlalchemy.pool --log-info=sqlalchemy.engine This would log mapper configuration, connection pool checkouts, and SQL @@ -225,7 +193,7 @@ Coverage is tracked using Nose's coverage plugin. See the nose documentation for details. Basic usage is: - $ nosetests test.sql.test_query --with-coverage + $ ./sqla_nose.py test.sql.test_query --with-coverage BIG COVERAGE TIP !!! There is an issue where existing .pyc files may store the incorrect filepaths, which will break the coverage system. If diff -r ce4da51d644c -r 35c36502a4c7 doc/build/core/engines.rst --- a/doc/build/core/engines.rst Wed Dec 15 13:25:16 2010 -0500 +++ b/doc/build/core/engines.rst Wed Dec 15 16:30:41 2010 -0500 @@ -43,7 +43,7 @@ backends; each is described as its own package in the :ref:`sqlalchemy.dialects_toplevel` package. A SQLAlchemy dialect always requires that an appropriate DBAPI driver is installed. -The table below summarizes the state of DBAPI support in SQLAlchemy 0.6. The values +The table below summarizes the state of DBAPI support in SQLAlchemy 0.7. The values translate as: * yes / Python platform - The SQLAlchemy dialect is mostly or fully operational on the target platform. @@ -209,19 +209,20 @@ sqlite_memory_db = create_engine('sqlite://') -The :class:`~sqlalchemy.engine.base.Engine` will ask the connection pool for a +The :class:`.Engine` will ask the connection pool for a connection when the ``connect()`` or ``execute()`` methods are called. The -default connection pool, :class:`~sqlalchemy.pool.QueuePool`, as well as the -default connection pool used with SQLite, -:class:`~sqlalchemy.pool.SingletonThreadPool`, will open connections to the +default connection pool, :class:`~.QueuePool`, will open connections to the database on an as-needed basis. As concurrent statements are executed, -:class:`~sqlalchemy.pool.QueuePool` will grow its pool of connections to a +:class:`.QueuePool` will grow its pool of connections to a default size of five, and will allow a default "overflow" of ten. Since the -:class:`~sqlalchemy.engine.base.Engine` is essentially "home base" for the +:class:`.Engine` is essentially "home base" for the connection pool, it follows that you should keep a single -:class:`~sqlalchemy.engine.base.Engine` per database established within an +:class:`.Engine` per database established within an application, rather than creating a new one for each connection. +.. note:: :class:`.QueuePool` is not used by default for SQLite engines. See + :ref:`sqlite_toplevel` for details on SQLite connection pool usage. + .. autoclass:: sqlalchemy.engine.url.URL :members: @@ -288,7 +289,7 @@ logging.basicConfig() logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) -By default, the log level is set to ``logging.ERROR`` within the entire +By default, the log level is set to ``logging.WARN`` within the entire ``sqlalchemy`` namespace so that no log operations occur, even within an application that has logging enabled otherwise. diff -r ce4da51d644c -r 35c36502a4c7 doc/build/core/event.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/build/core/event.rst Wed Dec 15 16:30:41 2010 -0500 @@ -0,0 +1,104 @@ +.. _event_toplevel: + +Events +====== + +SQLAlchemy includes an event API which publishes a wide variety of hooks into +the internals of both SQLAlchemy Core and ORM. The system is all new +as of version 0.7 and supercedes the previous system of "extension", "proxy", +and "listener" classes. + +Event Registration +------------------ + +Subscribing to an event occurs through a single API point, the :func:`.listen` function. This function +accepts a user-defined listening function, a string identifier which identifies the event to be +intercepted, and a target. Additional positional and keyword arguments may be supported by +specific types of events, which may specify alternate interfaces for the given event function, or provide +instructions regarding secondary event targets based on the given target. + +The name of an event and the argument signature of a corresponding listener function is derived from +a class bound specification method, which exists bound to a marker class that's described in the documentation. +For example, the documentation for :meth:`.PoolEvents.on_connect` indicates that the event name is ``"on_connect"`` +and that a user-defined listener function should receive two positional arguments:: + + from sqlalchemy.event import listen + from sqlalchemy.pool import Pool + + def my_on_connect(dbapi_con, connection_record): + print "New DBAPI connection:", dbapi_con + + listen(Pool, 'on_connect', my_on_connect) + +Targets +------- + +The :func:`.listen` function is very flexible regarding targets. It generally accepts classes, instances of those +classes, and related classes or objects from which the appropriate target can be derived. For example, +the above mentioned ``"on_connect"`` event accepts :class:`.Engine` classes and objects as well as :class:`.Pool` +classes and objects:: + + from sqlalchemy.event import listen + from sqlalchemy.pool import Pool, QueuePool + from sqlalchemy import create_engine + from sqlalchemy.engine import Engine + import psycopg2 + + def connect(): + return psycopg2.connect(username='ed', host='127.0.0.1', dbname='test') |