diff -Nru python-oslo.db-4.7.0/AUTHORS python-oslo.db-4.17.0/AUTHORS --- python-oslo.db-4.7.0/AUTHORS 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/AUTHORS 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,148 @@ +AKamyshnikova +Akihiro Motoki +Alessio Ababilov +Alexander Gorodnev +Alexandru Coman +Alexei Kornienko +Allison Randal +Alvaro Lopez Garcia +Andreas Jaeger +Andreas Jaeger +Andrew Laski +Andrew Melton +Andrey Kurilin +Angus Lees +Angus Salkeld +Angus Salkeld +Ann Kamyshnikova +Ben Nemec +Boris Bobrov +Boris Pavlovic +Brant Knudson +Brian Elliott +Bryan Jones +Cedric Brandily +Chang Bo Guo +ChangBo Guo(gcb) +Chris Behrens +Christian Berendt +Chuck Short +Cyril Roelandt +Davanum Srinivas +Davanum Srinivas +David Edery +David Peraza +Devananda van der Veen +Dima Shulyak +Dina Belova +Dirk Mueller +Dmitry Tantsur +Doug Hellmann +Doug Hellmann +Edan David +Eli Qiao +Eric Brown +Eric Guo +Eric Harney +Eric Windisch +Eugene Nikanorov +Eugeniya Kudryashova +Flavio Percoco +Florian Haas +Gary Kotton +Gary Kotton +Gregory Haynes +HanXue Lai +Henry Gessau +Huai Jiang +Ihar Hrachyshka +Ildiko +Ilya Pekelny +James Carey +Janonymous +Jasakov Artem +Jason Kölker +Jay Lau +Jay Pipes +Jeremy Stanley +Joe Gordon +Joe Heck +Johannes Erdfelt +Joshua Harlow +Joshua Harlow +Joshua Harlow +Julia Varlamova +Julian Sy +Julien Danjou +Kai Zhang +Kevin Benton +Kevin Benton +Lucas Alvares Gomes +Luis A. Garcia +Marco Fargetta +Mark McLoughlin +Matt Riedemann +Matthew Treinish +Max Lobur +Mehdi Abaakouk +Mehdi Abaakouk +Michael J Fork +Michael Wilson +Mike Bayer +Monty Taylor +Morgan Fainberg +Nikita Konovalov +Noorul Islam K M +Oleksii Chuprykov +Pavel Kholkin +Pekelny Ilya +Petr Blaho +Rajaram Mallya +Robert Collins +Roman Podoliaka +Roman Podolyaka +Roman Vasilets +Ronald Bradford +Sean Dague +Sergey Kraynev +Sergey Lukjanov +Sergey Nikitin +Shawn Boyette +Shuangtai Tian +Steve Kowalik +Steve Martinelli +Steven Hardy +Thomas Bechtold +Thomas Herve +Tianhua Huang +Tim Kelsey +Timofey Durakov +Tony Breeds +Tony Xu +Victor Sergeyev +Victor Stinner +Vlad Okhrimenko +Vladyslav Drok +Wu Wenxiang +Zhang Chun +Zhang Xin +Zhi Yan Liu +Zhongyue Luo +ahdj007 +bhagyashris +dineshbhor +ekudryashova +fumihiko kakuma +gengjh +howardlee +int32bit +melissaml +nandal +pkholkin +rossella +tengqm +venkatamahesh +wingwj +yan.haifeng +zhang-jinnan +zhangguoqing diff -Nru python-oslo.db-4.7.0/ChangeLog python-oslo.db-4.17.0/ChangeLog --- python-oslo.db-4.7.0/ChangeLog 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/ChangeLog 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,847 @@ +CHANGES +======= + +4.17.0 +------ + +* Modify word "whetever" to "whether" +* Updated from global requirements +* Add Constraints support +* Support packet sequence wrong error +* docs: mention that it's possible to use Connection directly +* exc_filters: fix deadlock detection for percona xtradb cluster +* Replaces uuid.uuid4 with uuidutils.generate_uuid() +* Fix marker checking when value is None +* Strip prefix `migrate_` in parameter `migrate_engine` + +4.16.0 +------ + +* migration: don't assume the mixin use provision +* Check if an index on given columns exists + +4.15.0 +------ + +* Ensure provision_new_database is True by default +* Don't run LegacyBaseClass provision test for unavailable database +* SoftDeleteMixin: allow for None values +* SoftDeleteMixin: coerce deleted param to be an integer +* Show team and repo badges on README +* Support MariaDB error 1927 +* Break optimize_db_test_loader into package and module level +* Adjust SAVEPOINT cause test for SQLA 1.1 +* Updated from global requirements +* Restore provisioning of DBs in legacy test base +* Updated from global requirements +* Updated from global requirements +* Enhanced fixtures for enginefacade-based provisioning +* utils: deprecate InsertFromSelect properly +* Using new style assertions instead of old style assertions +* Updated from global requirements +* Fix exc_filters for mysql-python +* Updated from global requirements +* Change assertTrue(isinstance()) by optimal assert +* OpenStack typo +* Changed the home-page link + +4.14.0 +------ + +* standardize release note page ordering +* Add a specific exception for 'unknown database' errors +* Enable release notes translation +* Updated from global requirements +* Add DBDataError for "Data too long" +* Updated from global requirements +* Updated from global requirements +* Add additional caution looking for table, info +* utils: fix get_unique_keys() when model has no table attached to it +* Update reno for stable/newton +* Updated from global requirements +* Correctly detect incomplete sort_keys passed to paginate_query +* Fix DBReferenceError and DBNonExistentTable with new PyMySQL version + +4.13.0 +------ + +* Updated from global requirements + +4.12.0 +------ + +* Updated from global requirements +* Link enginefacade to test database provisioning +* Display full reason for backend not available +* Updated from global requirements +* Deprecate argument sqlite_db in method set_defaults + +4.11.0 +------ + +* Updated from global requirements +* Add test helpers to enginefacade +* Add logging_name to enginefacade config + +4.10.0 +------ + +* Capture DatabaseError for deadlock check +* Add a hook to process newly created engines + +4.9.0 +----- + +* Updated from global requirements +* Memoize sys.exc_info() before attempting a savepoint rollback +* Updated from global requirements +* Fix parameters of assertEqual are misplaced +* Consolidate pifpaf commands into variables +* Updated from global requirements +* Updated from global requirements +* Fixed unit tests running on Windows +* Remove discover from setup.cfg +* Add dispose_pool() method to enginefacade context, factory + +4.8.0 +----- + +* Updated from global requirements +* Set a min and max on the connection_debug option +* Set max pool size default to 5 +* Add support for LONGTEXT, MEDIUMTEXT to JsonEncodedType +* tox: add py35 envs for convenience +* Deprecate config option sqlite_db for removal +* Catch empty value DBDuplicate errors +* release notes: mention changes in wrap_db_retry() +* Updated from global requirements +* api: use sane default in wrap_db_retry() +* Imported Translations from Zanata +* exc_filters: catch and translate non existent table on drop +* exception: make message mandatory in DbMigrationError and deprecates it +* Make it possible to use enginefacade decorators with class methods +* Updated from global requirements +* tests: fix order of assertEqual in exc_filter +* exc_filters: catch and translate non existent constraint on drop +* Replace tempest-lib dependency with os-testr +* Imported Translations from Zanata +* Fix typos in comments and docstring +* Updated from global requirements +* Updated from global requirements +* Fix typo: 'olso' to 'oslo' +* Repair boolean CHECK constraint detection +* api: do not log a traceback if error is not expected +* Fix imports in doc +* Allow testing of MySQL and PostgreSQL scenario locally +* Add support for custom JSON serializer +* api: always enable retry_on_request +* Remove oslo-incubator related stuff +* Updated from global requirements +* Updated from global requirements +* Remove direct dependency on babel +* Imported Translations from Zanata +* Add debtcollector to requirements +* Fix unit tests failures, when psycopg2 is not installed +* Fix server_default comparison for BigInteger +* Remove unused sqlite_fk in _init_connection_args call +* Updated from global requirements +* Correct docstring +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Raise DbMigrationError for invalid version +* Add new filter for DBDataError exception +* Fix spelling mistake +* Let enginefacade._TransactionContextManager look for context +* Remove sqlalchemy < 1.0.0 compatible layer +* Update reno for stable/mitaka +* Updated from global requirements +* Add tests for float interval values in wrap_db_retry() + +4.6.0 +----- + +* Increase the default max_overflow value +* Updated from global requirements +* add reno for release notes management +* Updated from global requirements +* Updated from global requirements +* Clarify the types for retry_interval args of wrap_db_retry + +4.5.0 +----- + +* Updated from global requirements +* stop making a copy of options discovered by config generator + +4.4.0 +----- + +* exceptions: provide .message attribute for Py3K compatibility +* Updated from global requirements +* InvalidSortKey constructor change breaks Heat unittests +* exception: fix DBInvalidUnicodeParameter error message +* exceptions: enhance InvalidSortKey to carry the invalid key +* exception: fix InvalidSortKey message +* Update translation setup +* Updated from global requirements +* Add exc_filter for invalid Unicode string +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements + +4.3.1 +----- + +* Imported Translations from Zanata +* Updated from global requirements +* Fix tests to work under both pymsysql 0.6.2 and 0.7.x +* Don't log non-db error in retry wrapper + +4.3.0 +----- + +* Updated from global requirements +* Put py34 first in the env order of tox +* Updated from global requirements + +4.2.0 +----- + +* Fix comparison of Variant and other type in compare_type +* Updated from global requirements +* Updated from global requirements +* Don't trace DB errors when we're retrying +* Updated from global requirements +* Remove iso8601 in requirements.txt +* Trival: Remove 'MANIFEST.in' + +4.1.0 +----- + +* Refactor deps to use extras and env markers +* Added allow_async property + +4.0.0 +----- + +* Updated from global requirements +* Remove python 2.6 classifier +* Remove python 2.6 and cleanup tox.ini + +3.2.0 +----- + +* Detect not-started _TransactionFactory in legacy +* Added method get_legacy_facade() to the _TransactionContextManager +* Removed Unused variable 'drivertype' +* Updated from global requirements +* Imported Translations from Zanata +* Updated from global requirements + +3.1.0 +----- + +* Updated from global requirements +* Updated from global requirements +* Add debug logging for DB retry attempt + +3.0.0 +----- + +* Fix coverage configuration and execution +* Add universal wheel tag to setup.cfg +* No need for Oslo Incubator Sync +* Updated from global requirements +* Correct invalid reference +* Imported Translations from Zanata +* Use stevedore directive to document plugins +* Make readme and documentation titles consistent +* Docstring fixes for enginefacade +* Fix warnings in docstrings +* Autogenerate the module docs +* Add config options to the documentation +* Add support for pickling enginefacade context objects +* Change ignore-errors to ignore_errors +* Fix the home-page value with Oslo wiki page +* Updated from global requirements +* Imported Translations from Zanata + +2.6.0 +----- + +* Imported Translations from Transifex +* Handle case where oslo_db.tests has not been imported +* Updated from global requirements + +2.5.0 +----- + +* Updated from global requirements +* Imported Translations from Transifex +* Updated from global requirements +* Move runtime test resources into setup.cfg [extras] +* Updated from global requirements + +2.4.1 +----- + +* Assume relative revisions belong to alembic +* Use correct config key in alembic extension +* Fix exception message about unavailable backend + +2.4.0 +----- + +* Imported Translations from Transifex +* Updated from global requirements +* Updated from global requirements +* Fix hacking rules and docs job +* Imported Translations from Transifex +* pagination: enhance sorting of null values +* Upgrade and downgrade based on revision existence +* Imported Translations from Transifex +* Updated from global requirements +* Add JSON-encoded types for sqlalchemy + +2.3.0 +----- + +* Imported Translations from Transifex +* Python 3: Use use_unicode=1 under Python 3 +* Imported Translations from Transifex +* Updated from global requirements +* Fix test_migrations on Python 3 +* Improve failure mode handling in enginefacade + +2.2.0 +----- + +* Imported Translations from Transifex +* Updated from global requirements +* Updated from global requirements +* Add mock to test-requirements.txt +* Test that concurrent sqlalchemy transactions don't block +* Updated from global requirements +* Added catching of errors 1047 (Galera) for MySQL oslo db reconnect +* Remove outdated tox environments for SQLAlchemy 0.8 +* Imported Translations from Transifex + +2.1.0 +----- + +* Allow projects that use test_models_sync to filter some changes +* Updated from global requirements +* Add legacy get_sessionmaker() method + +2.0.0 +----- + +* Fix sqlalchemy.ModelBase.__contains__() behaviour +* Add tox target to find missing requirements +* Allow additional exceptions in wrap_db_retry +* Remove implicit RequestContext decoration +* Add a new ModelBase.items() method +* Updated from global requirements +* Add oslo.context to requirements.txt +* Imported Translations from Transifex + +1.12.0 +------ + +* Updated from global requirements +* Remove oslo namespace package +* Drop use of 'oslo' namespace package +* Switch from MySQL-python to PyMySQL +* Updated from global requirements +* Switch badges from 'pypip.in' to 'shields.io' +* Updated from global requirements + +1.11.0 +------ + +* Replace utils method with oslo.utils reflection provided one +* Implement new oslo.db.sqlalchemy.enginefacade module +* Allow to fail instead of skip in DbFixture + +1.10.0 +------ + +* Updated from global requirements +* Imported Translations from Transifex +* Add a keys() method to SQLAlchemy ModelBase +* Remove support for Python 3.3 +* Updated from global requirements +* Remove run_cross_tests.sh +* Sort model fields using getattr(), not inspect() +* Imported Translations from Transifex +* Updated from global requirements +* Remove pre-SQLAlchemy-0.9.7 compat utilities +* Add Python 3 classifiers to setup.cfg + +1.9.0 +----- + +* Uncap library requirements for liberty + +1.8.0 +----- + +* Sanity check after migration +* Add filters for DBDataError exception +* Add pypi download + version badges +* exc_filters: support for ForeignKey error on delete +* Standardize setup.cfg summary for oslo libs +* Update to latest hacking +* Handle CHECK constraint integrity in PostgreSQL +* Catch DBDuplicateError in MySQL if primary key is binary +* Imported Translations from Transifex +* Updated from global requirements +* Imported Translations from Transifex +* Provide working SQLA_VERSION attribute +* Avoid excessing logging of RetryRequest exception +* Fixed bug in InsertFromSelect columns order +* Add process guards + invalidate to the connection pool + +1.7.0 +----- + +* Switch to non-namespaced module import - oslo_i18n +* Fix documented env variable for test connection +* Updated from global requirements +* Implement generic update-on-match feature + +1.6.0 +----- + +* Updated from global requirements + +1.5.0 +----- + +* Make DBAPI class work with mocks correctly +* Updated from global requirements +* Imported Translations from Transifex +* Fix PyMySQL reference error detection +* Use PyMySQL as DB driver in py3 environment +* Updated from global requirements +* Organize provisioning to use testresources +* Add retry decorator allowing to retry DB operations on request +* Imported Translations from Transifex +* Implement backend-specific drop_all_objects for provisioning +* Ensure that create_engine() close test connection +* Refactor database migration manager to use given engine +* Fix 0 version handling in migration_cli manager +* Updated from global requirements +* Fix PatchStacktraceTest for pypy +* Update Oslo imports to remove namespace package +* Retry query if db deadlock error is received + +1.4.1 +----- + +* Restore the check_foreign_keys() method +* Ensure DBConnectionError is raised on failed revalidate + +1.4.0 +----- + +* Fix slowest test output after test run +* Updated from global requirements +* Make sure sort_key_attr is QueryableAttribute when query +* Ensure mysql_sql_mode is set for MySQLOpportunisticTests +* Add pretty_tox wrapper script +* Fix PatchStacktraceTest test +* Ensure PostgreSQL connection errors are wrapped +* Remove check_foreign_keys from ModelsMigrationsSync +* Move files out of the namespace package +* Updated from global requirements +* Fix the link to the bug reporting site + +1.3.0 +----- + +* Repair string-based disconnect filters for MySQL, DB2 +* Fix python3.x scoping issues with removed 'uee' variable +* Updated from global requirements +* Fix test_migrate_cli for py3 +* Fix TestConnectionUtils to py3x compatibility +* Updated from global requirements +* Upgrade exc_filters for 'engine' argument and connect behavior +* Workflow documentation is now in infra-manual + +1.2.0 +----- + +* Imported Translations from Transifex +* Fix nested() for py3 +* Make test_models pass on py3 +* Repair include_object to accommodate new objects +* Add table name to foreign keys diff +* Updated from global requirements +* Handle Galera deadlock on SELECT FOR UPDATE +* Add exception filter for _sqlite_dupe_key_error +* Add info on how to run unit tests +* Ensure is_backend_avail() doesn't leave open connections +* Updated from global requirements + +1.1.0 +----- + +* Imported Translations from Transifex +* Add pbr to installation requirements +* Updated from global requirements +* Activate pep8 check that _ is imported +* Assert exceptions based on API, not string messages +* Fix python3.x scoping issues with removed 'de' variable +* Updated from global requirements +* Updated from global requirements +* Reorganize DbTestCase to use provisioning completely +* Set utf8 encoding for mysql and postgresql +* ModelsMigrationsSync: Add check for foreign keys +* Updated from global requirements +* Remove extraneous vim editor configuration comments +* Remove utils.drop_unique_constraint() +* Improve error reporting for backend import failures +* Ensure create_engine() retries the initial connection test +* Imported Translations from Transifex +* Use fixture from oslo.config instead of oslo-incubator +* Move begin ping listener to a connect listener +* Create a nested helper function that will work on py3.x +* Imported Translations from Transifex +* Start adding a environment for py34/py33 +* Explicitly depend on six in requirements file +* Unwrap DialectFunctionDispatcher from itself +* Updated from global requirements +* Use six.wraps instead of functools.wraps +* Update help string to use database + +1.0.1 +----- + +* Use __qualname__ if we can +* Fixup Fixtures Use in db test classes +* Add description for test_models_sync function +* Use the six provided iterator mix-in +* ModelsMigrationsSync:add correct server_default check for Enum + +1.0.0 +----- + +* Updated from global requirements +* Imported Translations from Transifex +* Add history/changelog to docs +* Add a check for SQLite transactional state +* Add run_cross_tests.sh script +* Let oslotest manage the six.move setting for mox +* Fix DBReferenceError on MySQL and SQLite +* Renaming in WalkVersionsMixin +* Clean up documentation +* Use single quotes for db schema sanity check +* warn against sorting requirements +* ModelsMigrationsSync:Override compare_server_default +* Updated from global requirements +* Imported Translations from Transifex +* Add doc8 to tox environment docs +* Use oslo.i18n +* Repair pysqlite transaction support +* Extract logging setup into a separate function +* Updated from global requirements +* Remove reliance on create_engine() from TestsExceptionFilter +* Consolidate sqlite and mysql event listeners +* Use dialect dispatch for engine initiailization +* Add get_non_innodb_tables() to utils +* Added check to see whether oslotest is installed + +0.4.0 +----- + +* Implement a dialect-level function dispatch system +* Move to oslo.utils +* Restore correct source file encodings +* Handle DB2 SmallInteger type for change_deleted_column_type_to_boolean +* Imported Translations from Transifex +* Fixes comments to pass E265 check +* Fixes indentations to pass E128 check +* Uses keyword params for i18n string to pass H703 +* Adds empty line to multilines docs to pass H405 +* Updates one line docstring with dot to pass H402 +* Changes import orders to pass H305 check +* Fixed DeprecationWarning in exc_filters +* Imported Translations from Transifex +* oslo.db.exceptions module documentation +* Updated from global requirements +* Extension of DBDuplicateEntry exception +* oslo.db.options module documentation +* oslo.db.api module documentation +* Imported Translations from Transifex +* Use SQLAlchemy cursor execute events for tracing +* Remove sqla_07 from tox.ini +* Updated from global requirements +* Specify raise_on_warnings=False for mysqlconnector +* Make MySQL regexes generic across MySQL drivers +* Allow tox tests with complex OS_TEST_DBAPI_CONNECTION URLs +* Raise DBReferenceError on foreign key violation +* Add host argument to get_connect_string() +* Imported Translations from Transifex +* Don't drop pre-existing database before tests +* Port _is_db_connection_error check to exception filters +* Integrate the ping listener into the filter system +* Add disconnect modification support to exception handling +* Implement new exception interception and filtering layer +* Implement the SQLAlchemy ``handle_error()`` event +* Remove moxstubout.py from oslo.db +* Added check for DB2 deadlock error +* Bump hacking to version 0.9.2 +* Opportunistic migration tests +* Move all db exception to exception.py +* Enable skipped tests from test_models.py +* Use explicit loops instead of list comprehensions +* Imported Translations from Transifex +* Allow usage of several iterators on ModelBase +* Add DBDuplicateEntry detection for mysqlconnector driver +* Check for mysql_sql_mode is not None in create_engine() + +0.3.0 +----- + +* Add a base test case for DB schema comparison +* Test for distinct SQLAlchemy major releases +* Updated from global requirements +* Add __contains__ to ModelBase to fully behave like a dict +* Fix test to not assume eventlet isn't present +* Avoid usage of mutables as default args +* Updated from global requirements + +0.2.0 +----- + +* Fix kwarg passed twice error in EngineFacade.from_config() + +0.1.0 +----- + +* Add psycopg2 to test-requirements.txt +* Adding dependency documentation for MySQL +* Prevent races in opportunistic db test cases +* Fix Sphinx directive name +* Bump hacking to 0.9.x series +* Add _wrap_db_error support for postgresql +* Handle slave database connection in EngineFacade +* Add eventlet.tpool.Proxy for DB API calls +* Added ``docs`` environment to tox.ini +* Setup for translation +* Remove common context from oslo.db +* Remove common context usage from db model_query() +* replace string format arguments with function parameters +* Make get_session() pass kwargs to a sessionmaker +* Allow for skipping thread_yielding +* Add index modifying methods +* Log a cause of db backend connection failure +* Do not always adjust sqlalchemy.engine logging +* Fix the test using in-file SQLite database +* Updated from global requirements +* cleaning up index.rst file +* Fix usage of oslo.config +* Add import_exceptions to tox.ini +* Fix changing the type of column deleted +* Remove redundant default=None for config options +* remove definitions of Python Source Code Encoding +* Improve help strings +* Ignore oslo.db.egg-info +* Allow cover tests to work +* Fix wrong method name with assert_called_once_with +* Fix call to mock.assert_not_called() +* Remove obsolete entries from .gitignore +* Remove patch_migrate() +* Fix typos: Remove extra ")" in message +* Fix .gitreview for oslo.db +* Fix dhellmann's notes from April 18 +* Make the tests passing +* Fix the graduate.sh script result +* Prevent races in opportunistic db test cases +* Drop dependency oslo.db from common.log +* Use oslotest instead of common test module +* Start ping listener also for postgresql +* Add a warning to not use get_table for working with ForeignKeys +* Ignore migrate versioning tables in utf8 sanity check +* Fix sqlalchemy utils test cases for SA 0.9.x +* Fix Keystone doc build errors with SQLAlchemy 0.9 +* Make table utf-8 charset checking be optional for DB migration +* Dispose db connections pool on disconnect +* Python3: pass bytes as 'contents' to create_tempfiles() +* Do not use the 'extend' method on a dict_items object +* Set sql_mode callback on connect instead of checkout +* Fix excessive logging from db.sqlalchemy.session +* Add lockutils fixture to OpportunisticTestCase +* Move test_insert_from_select unit test from nova to oslo +* Adapt DB provisioning code for CI requirements +* Make db utils importable without migrate +* Remove requirements.txt from .gitignore +* Get mysql_sql_mode parameter from config +* Prevent incorrect usage of _wrap_db_error() +* Python3: define a __next__() method for ModelBase +* Add from_config() method to EngineFacade +* db: move all options into database group +* Drop special case for MySQL traditional mode, update unit tests +* Make TRADITIONAL the default SQL mode +* Introduce mysql_sql_mode option, remove old warning +* Introduce a method to set any MySQL session SQL mode +* Handle ibm_db_sa DBDuplicateEntry integrity errors +* Fix doc build errors in db.sqlalchemy +* Fix migration.db_version when no tables +* Update log translation domains +* Add model_query() to db.sqlalchemy.utils module +* Fix a small typo in api.py +* migration.db_sync requires an engine now +* Remove CONF.database.connection default value +* Remove None for dict.get() +* Fix duplicating of SQL queries in logs +* Update oslo log messages with translation domains +* Restore the ability to load the DB backend lazily +* Don't use cfg.CONF in oslo.db +* Don't store engine instances in oslo.db +* Add etc/openstack.conf.sample to .gitignore +* py3kcompat: remove +* Don't raise MySQL 2013 'Lost connection' errors +* Format sql in db.sqlalchemy.session docstring +* Handle exception messages with six.text_type +* Drop dependency on log from oslo db code +* Automatic retry db.api query if db connection lost +* Clean up docstring in db.sqlalchemy.session +* Only enable MySQL TRADITIONAL mode if we're running against MySQL +* Move db tests base.py to common code +* Fix parsing of UC errors in sqlite 3.7.16+/3.8.2+ +* Use dialect rather than a particular DB API driver +* Move helper DB functions to db.sqlalchemy.utils +* Small edits on help strings +* Transition from migrate to alembic +* Fix mocking of utcnow() for model datetime cols +* Add a db check for CHARSET=utf8 +* Remove unused variables +* Remove "vim: tabstop=4 shiftwidth=4 softtabstop=4" from headers +* Fix database connection string is secret +* Cleanup unused log related code +* Removed copyright from empty files +* Fix the obsolete exception message +* Fix filter() usage due to python 3 compability +* Use hacking import_exceptions for gettextutils._ +* Add docstring for exception handlers of session +* Removal of _REPOSITORY global variable +* Remove string.lowercase usage +* Remove eventlet tpool from common db.api +* Database hook enabling traditional mode at MySQL +* Replace xrange in for loop with range +* SQLAlchemy error patterns improved +* Remove unused import +* Correct invalid docstrings +* Remove start index 0 in range() +* Make _extra_keys a property of ModelBase +* Fix mis-spellings +* Fix violations of H302:import only modules +* Enables db2 server disconnects to be handled pessimistically +* db.sqlalchemy.session add [sql].idle_timeout +* Use six.iteritems to make dict work on Python2/3 +* Trivial: Make vertical white space after license header consistent +* Drop dependency on processutils from oslo db code +* Fix locking in migration tests +* Incorporating MIT licensed code +* Typos fix in db and periodic_task module +* Use six.moves.configparser instead of ConfigParser +* Drop dependency on fileutils from oslo db tests +* fix typo in db session docstring +* Added opportunistic DB test cases +* The ability to run tests at various backend +* Use log.warning() instead of log.warn() in oslo.db +* Replace removed items in Python3 +* Remove vim header +* Use py3kcompat urlutils functions instead of urlparse +* Don't use deprecated module commands +* Remove sqlalchemy-migrate 0.7.3 patching +* SQLite behavior independent DB test cases +* Drop dependency on lockutils from oslo db code +* Remove lazy loading of database backend +* Do not name variables as builtins +* Add db2 communication error code when check the db connection +* Replace using tests.utils part3 +* Add [sql].connection as deprecated opt for db +* Modify SQLA session due to dispose of eventlet +* Use monkey_patch() in TestMigrationUtils setUp() +* Clean up db.sqla.Models.extra_keys interface +* Use functools.wrap() instead of custom implementation +* Move base migration test classes to common code +* Bump hacking to 0.7.0 +* exception: remove +* Replace using tests.utils with openstack.common.test +* Use single meta when change column type +* Helper function to sanitize db url credentials +* BaseException.message is deprecated since Python 2.6 +* Add function drop_unique_constraint() +* Change sqlalchemy/utils.py mode back to 644 +* Move sqlalchemy migration from Nova +* Allow use of hacking 0.6.0 and enable new checks +* Add eclipse project files to .gitignore +* Raise ValueError if sort_dir is unknown +* Add tests for cinder/common/sqlalchemyutils.py +* python3: Add python3 compatibility support +* Add .testrepository to .gitignore +* Move `test_migrations` from Nova +* Migrate sqlalchemy utils from Nova +* Enable H302 hacking check +* Add a monkey-patching util for sqlalchemy-migrate +* Don't use mixture of cfg.Opt() deprecated args +* Allow BaseTestCase use a different conf object +* Ensure that DB configuration is backward compatible +* Add a fixture for using of SQLite in-memory DB +* Enable hacking H404 test +* Enable user to configure pool_timeout +* Changed processing unique constraint name +* Enable H306 hacking check +* Add a slave db handle for the SQLAlchemy backend +* Enable hacking H403 test +* Changed processing unique constraint name +* Ignore backup files in .gitignore +* Specify database group instead of DEFAULT +* Fixes import order nits +* Line wrapper becomes to long when expanded +* Convert unicode for python3 portability +* Add test coverage for sqlite regexp function +* Use range rather than xrange +* Add support to clear DB +* Add enforcement for foreign key contraints with sqlite +* Improve Python 3.x compatibility +* Removes metadata from ModelBase +* Removes created_at, updated_at from ModelBase +* Fixes private functions private +* Mark sql_connection with secret flag +* Fix Copyright Headers - Rename LLC to Foundation +* Fixes import order nits +* Clean up sqlalchemy exception code +* Move DB thread pooling to DB API loader +* Use oslo-config-2013.1b3 +* Add join_consumer_pool() to RPC connections +* Use importutils.try_import() for MySQLdb +* Minor tweak to make update.py happy +* Remove pointless use of OpenStackException +* Remove unused context from test_sqlalchemy +* Remove openstack.common.db.common +* Provide creating real unique constraints for columns +* Fix missing wrap_db_error for Session.execute() method +* Fix eventlet/mysql db pooling code +* Add missing DBDuplicateEntry +* Be explicit about set_default() parameters +* Remove duplicate DB options +* Eliminate gratuitous DB difference vs Nova +* Import sqlalchemy session/models/utils +* updating sphinx documentation +* Correcting openstack-common mv to oslo-incubator +* Update .gitreview for oslo +* .gitignore updates for generated files +* Updated tox config for multi-python testing +* Added .gitreview file +* ignore cover's html directory +* Rajaram/Vinkesh|increased tests for Request and Response serializers +* Rajaram/Vinkesh|Added nova's serializaiton classes into common +* Initial skeleton project diff -Nru python-oslo.db-4.7.0/CONTRIBUTING.rst python-oslo.db-4.17.0/CONTRIBUTING.rst --- python-oslo.db-4.7.0/CONTRIBUTING.rst 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/CONTRIBUTING.rst 2017-01-18 14:09:32.000000000 +0000 @@ -59,4 +59,31 @@ mysql> GRANT ALL PRIVILEGES ON * . * TO 'openstack_citest'@'localhost'; mysql> FLUSH PRIVILEGES; +Alternatively, you can use `pifpaf`_ to run the unit tests directly without +setting up the database yourself. You still need to have the database software +installed on your system. The following tox environments can be used:: + + tox -e py27-mysql + tox -e py27-postgresql + tox -e py34-mysql + tox -e py34-postgresql + tox -e py27-all + tox -e py34-all + +The database will be set up for you locally and temporarily on each run. + +Another way is to start `pifpaf` manually and use it to run the tests as you +wish:: + + $ eval `pifpaf -g OS_TEST_DBAPI_ADMIN_CONNECTION run postgresql` + $ echo $OS_TEST_DBAPI_ADMIN_CONNECTION + postgresql://localhost/postgres?host=/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpMGqN8C&port=9824 + $ tox -e py27 + […] + $ tox -e py34 + […] + # Kill pifpaf once you're done + $ kill $PIFPAF_PID + .. _wiki: https://wiki.openstack.org/wiki/Testing#Unit_Tests +.. _pifpaf: https://github.com/jd/pifpaf diff -Nru python-oslo.db-4.7.0/debian/changelog python-oslo.db-4.17.0/debian/changelog --- python-oslo.db-4.7.0/debian/changelog 2016-04-05 21:02:43.000000000 +0000 +++ python-oslo.db-4.17.0/debian/changelog 2017-02-05 02:32:12.000000000 +0000 @@ -1,10 +1,102 @@ -python-oslo.db (4.7.0-2ubuntu1) xenial; urgency=medium +python-oslo.db (4.17.0-0ubuntu1~ubuntu16.04.1~ppa1) xenial; urgency=medium + + * No-change backport to xenial + + -- Chuck Short Sat, 04 Feb 2017 21:32:12 -0500 + +python-oslo.db (4.17.0-0ubuntu1) zesty; urgency=medium + + * New upstream release. + + -- Corey Bryant Thu, 19 Jan 2017 12:30:22 -0500 + +python-oslo.db (4.16.0-0ubuntu1) zesty; urgency=medium + + * New upstream version. + + -- Chuck Short Wed, 07 Dec 2016 10:04:16 -0500 + +python-oslo.db (4.15.0-0ubuntu1) zesty; urgency=medium + + * New upstream version. + * d/control: Bump version dependencies. + + -- Chuck Short Thu, 01 Dec 2016 15:10:19 -0500 + +python-oslo.db (4.14.0-0ubuntu1) zesty; urgency=medium + + [ Corey Bryant ] + * d/gbp.conf: Update gbp configuration file. + * d/control: Update Vcs-* links and maintainers. + + [ Chuck Short ] + * New upstream version. + * debian/control: Update dependencies. + * debian/tests: Add autopkgtest to test oslo.db against + a mysql database. + + -- Chuck Short Sun, 23 Oct 2016 20:37:42 -0400 + +python-oslo.db (4.13.3-1ubuntu1) yakkety; urgency=medium * Merge from Debian experimental, remaining changes: - - d/p/sqlalchemy_default_mysql_dialect.patch: Adjust tests to account - for Ubuntu sqlalchemy default mysql dialect being pymysql. + - d/p/pymysql-default.patch: Update unit tests to deal with pymysql + default in SQLAlchemy in Ubuntu, resolving FTBFS. + + -- James Page Mon, 19 Sep 2016 12:31:19 +0100 + +python-oslo.db (4.13.3-1) experimental; urgency=medium + + [ David Della Vecchia ] + * New upstream release. + * d/control: Align (build-)depends with upstream. + + [ Thomas Goirand ] + * New upstream release. + * Using OpenStack's Gerrit as VCS URLs. + * Using pkgos-dh_auto_{install,test} from openstack-pkg-tools >= 52~. + * Removed python-oslo-db and python-oslo-db-doc transition packages. + + -- Thomas Goirand Sun, 11 Sep 2016 18:00:43 +0200 + +python-oslo.db (4.13.0-0ubuntu2) yakkety; urgency=medium + + * Upload from ubuntu/newton branch. + + -- Corey Bryant Wed, 31 Aug 2016 10:10:11 -0400 + +python-oslo.db (4.13.0-0ubuntu1) yakkety; urgency=medium + + * New upstream release. + * d/control: Align (build-)depends with upstream. + + -- David Della Vecchia Tue, 30 Aug 2016 10:31:43 -0400 + +python-oslo.db (4.12.0-1ubuntu1) yakkety; urgency=medium + + * d/p/pymysql-default.patch: Update unit tests to deal with pymysql + default in SQLAlchemy in Ubuntu, resolving FTBFS. + + -- James Page Tue, 30 Aug 2016 15:17:42 +0100 + +python-oslo.db (4.12.0-1) experimental; urgency=medium + + [ Ondřej Nový ] + * Standards-Version is 3.9.8 now (no change) + * d/rules: Changed UPSTREAM_GIT protocol to https + * d/copyright: Changed source URL to https protocol - -- Corey Bryant Tue, 05 Apr 2016 17:02:36 -0400 + [ Corey Bryant ] + * New upstream release. + * d/gbp.conf: Update debian-branch for Newton. + * .gitreview: Copy file from orig tar. + * d/control: Align (Build-)Depends with upstream. + + [ James Page ] + * Align (Build-)Depends with upstream. + * New upstream release. + + -- Corey Bryant Thu, 25 Aug 2016 08:30:49 +0100 python-oslo.db (4.7.0-2) unstable; urgency=medium @@ -18,14 +110,6 @@ -- Thomas Goirand Sat, 02 Apr 2016 14:22:03 +0200 -python-oslo.db (4.6.0-1ubuntu1) xenial; urgency=medium - - * Merge from Debian experimental, remaining changes: - - d/p/sqlalchemy_default_mysql_dialect.patch: Adjust tests to account - for Ubuntu sqlalchemy default mysql dialect being pymysql. - - -- Corey Bryant Thu, 03 Mar 2016 12:02:41 -0500 - python-oslo.db (4.6.0-1) experimental; urgency=medium [ Ondřej Nový ] @@ -40,14 +124,6 @@ -- Thomas Goirand Wed, 02 Mar 2016 12:11:15 +0000 -python-oslo.db (4.3.0-1ubuntu1) xenial; urgency=medium - - * Merge from Debian experimental, remaining changes: - - d/p/sqlalchemy_default_mysql_dialect.patch: Adjust tests to account - for Ubuntu sqlalchemy default mysql dialect being pymysql. - - -- Corey Bryant Fri, 15 Jan 2016 14:15:03 -0500 - python-oslo.db (4.3.0-1) experimental; urgency=medium * New upstream release. @@ -64,23 +140,6 @@ -- Thomas Goirand Wed, 13 Jan 2016 01:55:45 +0000 -python-oslo.db (4.0.0-1ubuntu2) xenial; urgency=medium - - * Merge from Debian experimental, remaining changes: - - d/p/sqlalchemy_default_mysql_dialect.patch: Adjust tests to account - for Ubuntu sqlalchemy default mysql dialect being pymysql. - - d/control: Drop python(3)-mysqldb BDs. - - -- Corey Bryant Mon, 14 Dec 2015 09:12:48 -0500 - -python-oslo.db (4.0.0-1ubuntu1) xenial; urgency=medium - - * Merge from Debian experimental, remaining changes: - - d/p/sqlalchemy_default_mysql_dialect.patch: Adjust tests to account - for Ubuntu sqlalchemy default mysql dialect being pymysql. - - -- Corey Bryant Fri, 04 Dec 2015 13:05:37 -0500 - python-oslo.db (4.0.0-1) experimental; urgency=medium [ Corey Bryant ] @@ -193,3 +252,4 @@ * Initial release. (Closes: #752621) -- Thomas Goirand Wed, 25 Jun 2014 16:11:40 +0800 + diff -Nru python-oslo.db-4.7.0/debian/control python-oslo.db-4.17.0/debian/control --- python-oslo.db-4.7.0/debian/control 2016-04-05 21:03:18.000000000 +0000 +++ python-oslo.db-4.17.0/debian/control 2017-01-19 17:30:22.000000000 +0000 @@ -5,9 +5,10 @@ XSBC-Original-Maintainer: PKG OpenStack Uploaders: Thomas Goirand , Corey Bryant , + David Della Vecchia , Build-Depends: debhelper (>= 9), dh-python, - openstack-pkg-tools, + openstack-pkg-tools (>= 52~), python-all, python-pbr (>= 1.8), python-setuptools, @@ -15,71 +16,75 @@ python3-all, python3-pbr (>= 1.8), python3-setuptools, -Build-Depends-Indep: alembic (>= 0.8.0), - python-alembic (>= 0.8.0), +Build-Depends-Indep: alembic (>= 0.8.4), + python-alembic (>= 0.8.4), python-babel, python-coverage, + python-debtcollector (>= 1.2.0), python-eventlet (>= 0.18.4), - python-fixtures (>= 1.3.1), + python-fixtures (>= 3.0.0), python-hacking, python-migrate (>= 0.9.6), - python-mock (>= 1.3), - python-oslo.config (>= 1:3.7.0), - python-oslo.context (>= 0.2.0), + python-mock (>= 2.0), + python-os-testr (>= 0.7.0), + python-oslo.config (>= 1:3.14.0), + python-oslo.context (>= 2.9.0), python-oslo.i18n (>= 2.1.0), - python-oslo.utils (>= 3.5.0), - python-oslosphinx (>= 2.5.0), + python-oslo.utils (>= 3.18.0), + python-oslosphinx (>= 4.7.0), python-oslotest (>= 1.10.0), python-psycopg2 (>= 2.5), python-pymysql, python-six (>= 1.9.0), python-sqlalchemy (>= 1.0.10), - python-stevedore (>= 1.5.0), - python-tempest-lib (>= 0.14.0), + python-stevedore (>= 1.17.1), python-testresources, python-testtools (>= 1.4.0), - python3-alembic (>= 0.8.0), + python3-alembic (>= 0.8.4), python3-babel, + python3-debtcollector (>= 1.2.0), python3-eventlet (>= 0.18.4), - python3-fixtures (>= 1.3.1), + python3-fixtures (>= 3.0.0), python3-migrate (>= 0.9.6), - python3-mock (>= 1.3), - python3-oslo.config (>= 1:3.7.0), - python3-oslo.context (>= 0.2.0), + python3-mock (>= 2.0), + python3-os-testr (>= 0.8.0), + python3-oslo.config (>= 1:3.14.0), + python3-oslo.context (>= 2.9.0), python3-oslo.i18n (>= 2.1.0), - python3-oslo.utils (>= 3.5.0), + python3-oslo.utils (>= 3.18.0), python3-oslotest (>= 1.10.0), python3-psycopg2 (>= 2.5), python3-pymysql, python3-six (>= 1.9.0), python3-sqlalchemy (>= 1.0.10), - python3-stevedore (>= 1.5.0), + python3-stevedore (>= 1.16.0), python3-subunit, - python3-tempest-lib (>= 0.14.0), python3-testresources, python3-testtools (>= 1.4.0), subunit, testrepository, -Standards-Version: 3.9.7 -Vcs-Browser: https://anonscm.debian.org/cgit/openstack/python-oslo.db.git/ -Vcs-Git: https://anonscm.debian.org/git/openstack/python-oslo.db.git +Standards-Version: 3.9.8 +Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-oslo.db +Vcs-Git: git://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-oslo.db Homepage: https://launchpad.net/oslo +XS-Testsuite: autopkgtest Package: python-oslo.db Architecture: all Provides: python-oslo-db, -Depends: alembic (>= 0.8.0), - python-alembic (>= 0.8.0), +Depends: alembic (>= 0.8.4), + python-alembic (>= 0.8.4), python-babel, + python-debtcollector (>= 1.2.0), python-migrate (>= 0.9.6), - python-oslo.config (>= 1:3.7.0), - python-oslo.context (>= 0.2.0), + python-oslo.config (>= 1:3.14.0), + python-oslo.context (>= 2.9.0), python-oslo.i18n (>= 2.1.0), - python-oslo.utils (>= 3.5.0), + python-oslo.utils (>= 3.18.0), python-pbr (>= 1.8), python-six (>= 1.9.0), python-sqlalchemy (>= 1.0.10), - python-stevedore (>= 1.5.0), + python-stevedore (>= 1.17.1), ${misc:Depends}, ${python:Depends}, Breaks: python-oslo-db (<< 1.10.0-1~), @@ -94,18 +99,19 @@ Package: python3-oslo.db Architecture: all Provides: python-oslo-db, -Depends: alembic (>= 0.8.0), - python3-alembic (>= 0.8.0), +Depends: alembic (>= 0.8.4), + python3-alembic (>= 0.8.4), python3-babel, + python3-debtcollector (>= 1.2.0), python3-migrate (>= 0.9.6), - python3-oslo.config (>= 1:3.7.0), - python3-oslo.context (>= 0.2.0), + python3-oslo.config (>= 1:3.14.0), + python3-oslo.context (>= 2.9.0), python3-oslo.i18n (>= 2.1.0), - python3-oslo.utils (>= 3.5.0), + python3-oslo.utils (>= 3.18.0), python3-pbr (>= 1.8), python3-six (>= 1.9.0), python3-sqlalchemy (>= 1.0.10), - python3-stevedore (>= 1.5.0), + python3-stevedore (>= 1.17.1), ${misc:Depends}, ${python3:Depends}, Breaks: python-oslo-db (<< 1.10.0-1~), @@ -128,21 +134,3 @@ helper utils. . This package contains the documentation. - -Package: python-oslo-db -Section: oldlibs -Priority: extra -Architecture: all -Depends: python-oslo.db, - ${misc:Depends}, -Description: transitional dummy package for python-oslo-db - This transitional package is safe to remove. - -Package: python-oslo-db-doc -Section: oldlibs -Priority: extra -Architecture: all -Depends: python-oslo.db-doc, - ${misc:Depends}, -Description: transitional dummy package for python-oslo-db-doc - This transitional package is safe to remove. diff -Nru python-oslo.db-4.7.0/debian/copyright python-oslo.db-4.17.0/debian/copyright --- python-oslo.db-4.7.0/debian/copyright 2016-04-05 20:59:42.000000000 +0000 +++ python-oslo.db-4.17.0/debian/copyright 2017-01-19 17:30:22.000000000 +0000 @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: oslo.db -Source: http://launchpad.net/oslo +Source: https://launchpad.net/oslo Files: * Copyright: (c) 2013-2016, OpenStack Foundation diff -Nru python-oslo.db-4.7.0/debian/gbp.conf python-oslo.db-4.17.0/debian/gbp.conf --- python-oslo.db-4.7.0/debian/gbp.conf 2016-04-05 20:59:42.000000000 +0000 +++ python-oslo.db-4.17.0/debian/gbp.conf 2017-01-19 17:30:22.000000000 +0000 @@ -1,9 +1,7 @@ [DEFAULT] -upstream-branch = master -debian-branch = debian/mitaka +debian-branch = master upstream-tag = %(version)s -compression = xz +pristine-tar = True [buildpackage] -export-dir = ../build-area/ - +export-dir = ../build-area diff -Nru python-oslo.db-4.7.0/debian/patches/pymysql-default.patch python-oslo.db-4.17.0/debian/patches/pymysql-default.patch --- python-oslo.db-4.7.0/debian/patches/pymysql-default.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/debian/patches/pymysql-default.patch 2017-01-19 17:30:22.000000000 +0000 @@ -0,0 +1,70 @@ +Description: SQLAlchemy in Ubuntu defaults mysql to pymysql + Update unit tests to deal with this difference + + URL -> Call + mysql -> mysql_pymysql + mysql+pymysql -> mysql_pymysql + mysql+mysqlconnector -> mysql + mysql+mysqldb -> mysql + +Author: James Page +Forwarded: not-needed + +--- a/oslo_db/tests/sqlalchemy/test_utils.py ++++ b/oslo_db/tests/sqlalchemy/test_utils.py +@@ -1138,6 +1138,8 @@ class TestDialectFunctionDispatcher(test + dispatcher = dispatcher.dispatch_for("mysql+pymysql")( + callable_fn.mysql_pymysql) + dispatcher = dispatcher.dispatch_for("mysql")( ++ callable_fn.mysql_pymysql) ++ dispatcher = dispatcher.dispatch_for("mysql+mysqlconnector")( + callable_fn.mysql) + dispatcher = dispatcher.dispatch_for("postgresql")( + callable_fn.postgresql) +@@ -1166,7 +1168,7 @@ class TestDialectFunctionDispatcher(test + dispatcher = dispatcher.dispatch_for("mysql+pymysql")( + callable_fn.mysql_pymysql) + dispatcher = dispatcher.dispatch_for("mysql")( +- callable_fn.mysql) ++ callable_fn.mysql_pymysql) + dispatcher = dispatcher.dispatch_for("postgresql+*")( + callable_fn.postgresql) + dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")( +@@ -1192,7 +1194,7 @@ class TestDialectFunctionDispatcher(test + mock.call.sqlite('sqlite://', 1), + mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2), + mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3), +- mock.call.mysql("mysql://u:p@h/t", 4), ++ mock.call.mysql_pymysql("mysql://u:p@h/t", 4), + mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5), + ], + callable_fn.mock_calls) +@@ -1345,7 +1347,7 @@ class TestDialectFunctionDispatcher(test + + dispatcher(url, 15) + self.assertEqual( +- [mock.call.mysql(url, 15)], ++ [mock.call.mysql_pymysql(url, 15)], + callable_fn.mock_calls + ) + +@@ -1404,7 +1406,7 @@ class TestDialectFunctionDispatcher(test + mock.call.pyodbc('postgresql+pyodbc://', 1), + mock.call.default('postgresql+pyodbc://', 1), + mock.call.mysql_pymysql('mysql+pymysql://', 2), +- mock.call.mysql('mysql+pymysql://', 2), ++ mock.call.mysql_pymysql('mysql+pymysql://', 2), + mock.call.default('mysql+pymysql://', 2), + mock.call.default('ibm_db_sa+db2://', 3), + mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4), +--- a/oslo_db/tests/sqlalchemy/test_sqlalchemy.py ++++ b/oslo_db/tests/sqlalchemy/test_sqlalchemy.py +@@ -613,7 +613,7 @@ class CreateEngineTest(oslo_test.BaseTes + def test_mysql_connect_args_default(self): + engines._init_connection_args( + url.make_url("mysql://u:p@host/test"), self.args) +- self._test_mysql_connect_args_default(self.args['connect_args']) ++ self.assertEqual({'charset': 'utf8'}, self.args['connect_args']) + + def test_mysql_oursql_connect_args_default(self): + engines._init_connection_args( diff -Nru python-oslo.db-4.7.0/debian/patches/series python-oslo.db-4.17.0/debian/patches/series --- python-oslo.db-4.7.0/debian/patches/series 2016-04-05 21:04:00.000000000 +0000 +++ python-oslo.db-4.17.0/debian/patches/series 2017-01-19 17:30:22.000000000 +0000 @@ -1 +1 @@ -sqlalchemy_default_mysql_dialect.patch +pymysql-default.patch diff -Nru python-oslo.db-4.7.0/debian/patches/sqlalchemy_default_mysql_dialect.patch python-oslo.db-4.17.0/debian/patches/sqlalchemy_default_mysql_dialect.patch --- python-oslo.db-4.7.0/debian/patches/sqlalchemy_default_mysql_dialect.patch 2016-04-05 21:04:08.000000000 +0000 +++ python-oslo.db-4.17.0/debian/patches/sqlalchemy_default_mysql_dialect.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -Description: Ubuntu sqlalchemy defaults mysql to the pymysql dialect. - This patch adjust tests to account for this. -Author: Corey Bryant -Forwarded: No - ---- a/oslo_db/tests/sqlalchemy/test_sqlalchemy.py -+++ b/oslo_db/tests/sqlalchemy/test_sqlalchemy.py -@@ -609,7 +609,8 @@ - def test_mysql_connect_args_default(self): - engines._init_connection_args( - url.make_url("mysql://u:p@host/test"), self.args) -- self._test_mysql_connect_args_default(self.args['connect_args']) -+ self.assertEqual(self.args['connect_args'], -+ {'charset': 'utf8'}) - - def test_mysql_oursql_connect_args_default(self): - engines._init_connection_args( ---- a/oslo_db/tests/sqlalchemy/test_utils.py -+++ b/oslo_db/tests/sqlalchemy/test_utils.py -@@ -1004,7 +1004,7 @@ - mock.call.sqlite('sqlite://', 1), - mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2), - mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3), -- mock.call.mysql("mysql://u:p@h/t", 4), -+ mock.call.mysql_pymysql("mysql://u:p@h/t", 4), - mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5), - ], - callable_fn.mock_calls) -@@ -1157,7 +1157,7 @@ - - dispatcher(url, 15) - self.assertEqual( -- [mock.call.mysql(url, 15)], -+ [mock.call.mysql_pymysql(url, 15)], - callable_fn.mock_calls - ) - diff -Nru python-oslo.db-4.7.0/debian/rules python-oslo.db-4.17.0/debian/rules --- python-oslo.db-4.7.0/debian/rules 2016-04-05 20:59:42.000000000 +0000 +++ python-oslo.db-4.17.0/debian/rules 2017-01-19 17:30:22.000000000 +0000 @@ -1,41 +1,19 @@ #!/usr/bin/make -f -PYTHONS:=$(shell pyversions -vr) -PYTHON3S:=$(shell py3versions -vr) +UPSTREAM_GIT := https://github.com/openstack/oslo.db.git +include /usr/share/openstack-pkg-tools/pkgos.make -UPSTREAM_GIT = git://github.com/openstack/oslo.db.git --include /usr/share/openstack-pkg-tools/pkgos.make - -export OSLO_PACKAGE_VERSION=$(VERSION) export OSLO_LOCK_PATH=$(CURDIR)/debian/ %: dh $@ --buildsystem=python_distutils --with python2,python3,sphinxdoc -override_dh_install: - set -e ; for pyvers in $(PYTHONS); do \ - python$$pyvers setup.py install --install-layout=deb \ - --root $(CURDIR)/debian/python-oslo.db; \ - done - set -e ; for pyvers in $(PYTHON3S); do \ - python$$pyvers setup.py install --install-layout=deb \ - --root $(CURDIR)/debian/python3-oslo.db; \ - done +override_dh_auto_install: + pkgos-dh_auto_install override_dh_auto_test: ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS))) - @echo "===> Running tests" - set -e ; set -x ; for i in 2.7 $(PYTHON3S) ; do \ - PYMAJOR=`echo $$i | cut -d'.' -f1` ; \ - echo "===> Testing with python$$i (python$$PYMAJOR)" ; \ - rm -rf .testrepository ; \ - testr-python$$PYMAJOR init ; \ - TEMP_REZ=`mktemp -t` ; \ - PYTHONPATH=$(CURDIR) PYTHON=python$$i testr-python$$PYMAJOR run --subunit 'tests\.(?!.*test_db_api_without_installed_eventlet.*)' | tee $$TEMP_REZ | subunit2pyunit ; \ - cat $$TEMP_REZ | subunit-filter -s --no-passthrough | subunit-stats ; \ - rm -f $$TEMP_REZ ; \ - testr-python$$PYMAJOR slowest ; \ - done + pkgos-dh_auto_test 'tests\.(?!.*test_db_api_without_installed_eventlet.*)' endif override_dh_clean: diff -Nru python-oslo.db-4.7.0/debian/tests/control python-oslo.db-4.17.0/debian/tests/control --- python-oslo.db-4.7.0/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/debian/tests/control 2017-01-19 17:30:22.000000000 +0000 @@ -0,0 +1,7 @@ +Tests: mysql +Depends: alembic, python-babel, python-alembic, python-debtcollector, python-oslo.i18n, + python-oslo.config, python-oslo.context, python-oslo.utils, python-sqlalchemy, + python-migrate, python-stevedore, python-six, python-fixtures, python-mock, + python-oslotest, python-os-testr, python-tox, python-oslo.db, mysql-server-5.7, + python-testresources, python-pymysql, python-mysql.connector +Restrictions: allow-stderr needs-root breaks-testbed diff -Nru python-oslo.db-4.7.0/debian/tests/mysql python-oslo.db-4.17.0/debian/tests/mysql --- python-oslo.db-4.7.0/debian/tests/mysql 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/debian/tests/mysql 2017-01-19 17:30:22.000000000 +0000 @@ -0,0 +1,18 @@ +#!/bin/sh +set -ex + +debconf-set-selections <`_ +is preferred: + +.. code:: python + + @enginefacade.reader.connection + def _refresh_from_db(context, cache): + sel = sa.select([table.c.id, table.c.name]) + res = context.connection.execute(sel).fetchall() + cache.id_cache = {r[1]: r[0] for r in res} + cache.str_cache = {r[0]: r[1] for r in res} + + .. note:: The ``context.session`` and ``context.connection`` attributes must be accessed within the scope of an appropriate writer/reader block (either the decorator or contextmanager approach). An AttributeError is raised otherwise. +The decorator form can also be used with class and instance methods which +implicitly receive the first positional argument: + +.. code:: python + + class DatabaseAccessLayer(object): + + @classmethod + @enginefacade.reader + def some_reader_api_function(cls, context): + return context.session.query(SomeClass).all() + + @enginefacade.writer + def some_writer_api_function(self, context, x, y): + context.session.add(SomeClass(x, y)) + +.. note:: Note that enginefacade decorators must be applied **before** + `classmethod`, otherwise you will get a ``TypeError`` at import time + (as enginefacade will try to use ``inspect.getargspec()`` on a descriptor, + not on a bound method, please refer to the `Data Model + `_ section + of the Python Language Reference for details). + + The scope of transaction and connectivity for both approaches is managed transparently. The configuration for the connection comes from the standard :obj:`oslo_config.cfg.CONF` collection. Additional configurations can be @@ -105,7 +143,7 @@ .. code:: python - from oslo.db import models + from oslo_db.sqlalchemy import models class ProjectSomething(models.TimestampMixin, @@ -119,8 +157,8 @@ .. code:: python - from oslo.config import cfg - from oslo.db import api as db_api + from oslo_config import cfg + from oslo_db import api as db_api _BACKEND_MAPPING = {'sqlalchemy': 'project.db.sqlalchemy.api'} @@ -142,5 +180,5 @@ Available extensions for `oslo_db.migration`. -.. list-plugins:: oslo.db.migration +.. list-plugins:: oslo_db.sqlalchemy.migration :detailed: diff -Nru python-oslo.db-4.7.0/.gitignore python-oslo.db-4.17.0/.gitignore --- python-oslo.db-4.7.0/.gitignore 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -*~ -*.swp -*.pyc -*.log -.coverage -.venv -.tox -cover/ -.openstack-common-venv/ -skeleton.egg-info/ -build/ -dist/ -AUTHORS -.update-venv/ -ChangeLog -*.egg -.testrepository/ -.project -.pydevproject -oslo.db.egg-info/ -doc/source/api diff -Nru python-oslo.db-4.7.0/.gitreview python-oslo.db-4.17.0/.gitreview --- python-oslo.db-4.7.0/.gitreview 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/.gitreview 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/oslo.db.git -defaultbranch=stable/mitaka diff -Nru python-oslo.db-4.7.0/oslo_db/api.py python-oslo.db-4.17.0/oslo_db/api.py --- python-oslo.db-4.7.0/oslo_db/api.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/api.py 2017-01-18 14:09:32.000000000 +0000 @@ -27,6 +27,7 @@ import threading import time +from debtcollector import removals from oslo_utils import excutils from oslo_utils import importutils from oslo_utils import reflection @@ -106,22 +107,22 @@ :type exception_checker: callable """ - def __init__(self, retry_interval=0, max_retries=0, - inc_retry_interval=False, - max_retry_interval=0, retry_on_disconnect=False, + @removals.removed_kwarg("retry_on_request", + "Retry on request is always enabled") + def __init__(self, retry_interval=1, max_retries=20, + inc_retry_interval=True, + max_retry_interval=10, retry_on_disconnect=False, retry_on_deadlock=False, retry_on_request=False, exception_checker=lambda exc: False): super(wrap_db_retry, self).__init__() - self.db_error = () + self.db_error = (exception.RetryRequest, ) # default is that we re-raise anything unexpected self.exception_checker = exception_checker if retry_on_disconnect: self.db_error += (exception.DBConnectionError, ) if retry_on_deadlock: self.db_error += (exception.DBDeadlock, ) - if retry_on_request: - self.db_error += (exception.RetryRequest, ) self.retry_interval = retry_interval self.max_retries = max_retries self.inc_retry_interval = inc_retry_interval @@ -138,10 +139,12 @@ return f(*args, **kwargs) except Exception as e: with excutils.save_and_reraise_exception() as ectxt: + expected = self._is_exception_expected(e) if remaining > 0: - ectxt.reraise = not self._is_exception_expected(e) + ectxt.reraise = not expected else: - LOG.exception(_LE('DB exceeded retry limit.')) + if expected: + LOG.exception(_LE('DB exceeded retry limit.')) # if it's a RetryRequest, we need to unpack it if isinstance(e, exception.RetryRequest): ectxt.type_ = type(e.inner_exc) @@ -218,10 +221,11 @@ self._load_backend() self.use_db_reconnect = kwargs.get('use_db_reconnect', False) - self.retry_interval = kwargs.get('retry_interval', 1) - self.inc_retry_interval = kwargs.get('inc_retry_interval', True) - self.max_retry_interval = kwargs.get('max_retry_interval', 10) - self.max_retries = kwargs.get('max_retries', 20) + self._wrap_db_kwargs = {k: v for k, v in kwargs.items() + if k in ('retry_interval', + 'inc_retry_interval', + 'max_retry_interval', + 'max_retries')} def _load_backend(self): with self._lock: @@ -253,13 +257,9 @@ if retry_on_disconnect or retry_on_deadlock or retry_on_request: attr = wrap_db_retry( - retry_interval=self.retry_interval, - max_retries=self.max_retries, - inc_retry_interval=self.inc_retry_interval, - max_retry_interval=self.max_retry_interval, retry_on_disconnect=retry_on_disconnect, retry_on_deadlock=retry_on_deadlock, - retry_on_request=retry_on_request)(attr) + **self._wrap_db_kwargs)(attr) return attr diff -Nru python-oslo.db-4.7.0/oslo_db/exception.py python-oslo.db-4.17.0/oslo_db/exception.py --- python-oslo.db-4.7.0/oslo_db/exception.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/exception.py 2017-01-18 14:09:32.000000000 +0000 @@ -47,9 +47,10 @@ import six from oslo_db._i18n import _ +from oslo_utils.excutils import CausedByException -class DBError(Exception): +class DBError(CausedByException): """Base exception for all custom database exceptions. @@ -57,9 +58,9 @@ DBError or its subclasses. """ - def __init__(self, inner_exception=None): + def __init__(self, inner_exception=None, cause=None): self.inner_exception = inner_exception - super(DBError, self).__init__(six.text_type(inner_exception)) + super(DBError, self).__init__(six.text_type(inner_exception), cause) class DBDuplicateEntry(DBError): @@ -127,6 +128,45 @@ super(DBReferenceError, self).__init__(inner_exception) +class DBNonExistentConstraint(DBError): + """Constraint does not exist. + + :param table: table name + :type table: str + :param constraint: constraint name + :type table: str + """ + + def __init__(self, table, constraint, inner_exception=None): + self.table = table + self.constraint = constraint + super(DBNonExistentConstraint, self).__init__(inner_exception) + + +class DBNonExistentTable(DBError): + """Table does not exist. + + :param table: table name + :type table: str + """ + + def __init__(self, table, inner_exception=None): + self.table = table + super(DBNonExistentTable, self).__init__(inner_exception) + + +class DBNonExistentDatabase(DBError): + """Database does not exist. + + :param database: database name + :type database: str + """ + + def __init__(self, database, inner_exception=None): + self.database = database + super(DBNonExistentDatabase, self).__init__(inner_exception) + + class DBDeadlock(DBError): """Database dead lock error. @@ -171,6 +211,20 @@ super(DbMigrationError, self).__init__(message) +class DBMigrationError(DbMigrationError): + + """Wrapped migration specific exception. + + Raised when migrations couldn't be completed successfully. + """ + def __init__(self, message): + super(DBMigrationError, self).__init__(message) + + +debtcollector.removals.removed_class(DbMigrationError, + replacement=DBMigrationError) + + class DBConnectionError(DBError): """Wrapped connection specific exception. diff -Nru python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-error.po python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-error.po --- python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-error.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-error.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Andi Chandler , 2014-2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,9 +24,6 @@ msgid "'eventlet' is required for TpoolDbapiWrapper." msgstr "'eventlet' is required for TpoolDbapiWrapper." -msgid "DB error." -msgstr "DB error." - msgid "DB exceeded retry limit." msgstr "DB exceeded retry limit." diff -Nru python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-info.po python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-info.po --- python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-info.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-info.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Andi Chandler , 2014 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-warning.po python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-warning.po --- python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-warning.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-warning.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Andi Chandler , 2014-2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db.po python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db.po --- python-oslo.db-4.7.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/en_GB/LC_MESSAGES/oslo_db.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,17 +4,17 @@ # # Translators: # Andi Chandler , 2014-2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev46\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-06-15 11:18+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2015-08-27 12:47+0000\n" -"Last-Translator: Andi Chandler \n" +"PO-Revision-Date: 2016-06-20 06:31+0000\n" +"Last-Translator: Andreas Jaeger \n" "Language: en-GB\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" @@ -32,8 +32,9 @@ "Please specify column %s in col_name_col_instance param. It is required " "because column has unsupported type by SQLite." -msgid "Sort key supplied was not valid." -msgstr "Sort key supplied was not valid." +#, python-format +msgid "Sort key supplied is invalid: %s" +msgstr "Sort key supplied is invalid: %s" #, python-format msgid "" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-error.po python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-error.po --- python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-error.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-error.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Adriana Chisco Landazábal , 2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,9 +24,6 @@ msgid "'eventlet' is required for TpoolDbapiWrapper." msgstr "Se necesita 'eventlet' para TpoolDbapiWrapper." -msgid "DB error." -msgstr "Error de base de datos" - msgid "DB exceeded retry limit." msgstr "La base de datos excedió el límite de intentos." diff -Nru python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-info.po python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-info.po --- python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-info.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-info.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Adriana Chisco Landazábal , 2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-warning.po python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-warning.po --- python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-warning.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db-log-warning.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Adriana Chisco Landazábal , 2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db.po python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db.po --- python-oslo.db-4.7.0/oslo_db/locale/es/LC_MESSAGES/oslo_db.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/es/LC_MESSAGES/oslo_db.po 2017-01-18 14:09:32.000000000 +0000 @@ -5,12 +5,12 @@ # Translators: # Adriana Chisco Landazábal , 2015 # Miriam Godinez , 2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -33,9 +33,6 @@ "Por favor especifique la columna %s en el parámetro col_name_col_instance. " "Es necesario porque la columna tiene un tipo no soportado por SQLite." -msgid "Sort key supplied was not valid." -msgstr "La clave de ordenación proporcionada no es válida. " - #, python-format msgid "" "Tables \"%s\" have non utf8 collation, please make sure all tables are " diff -Nru python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-error.po python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-error.po --- python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-error.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-error.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Maxime COQUEREL , 2014-2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,9 +24,6 @@ msgid "'eventlet' is required for TpoolDbapiWrapper." msgstr "'eventlet' est requis pour poolDbapiWrapper." -msgid "DB error." -msgstr "Erreur DB." - msgid "DB exceeded retry limit." msgstr "DB limite de tentatives dépassé." diff -Nru python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-info.po python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-info.po --- python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-info.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-info.po 2017-01-18 14:09:32.000000000 +0000 @@ -4,12 +4,12 @@ # # Translators: # Maxime COQUEREL , 2014 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-warning.po python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-warning.po --- python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-warning.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-warning.po 2017-01-18 14:09:32.000000000 +0000 @@ -5,12 +5,12 @@ # Translators: # Lucas Mascaro , 2015 # Maxime COQUEREL , 2014 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff -Nru python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db.po python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db.po --- python-oslo.db-4.7.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db.po 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/fr/LC_MESSAGES/oslo_db.po 2017-01-18 14:09:32.000000000 +0000 @@ -5,12 +5,12 @@ # Translators: # Lucas Mascaro , 2015 # Maxime COQUEREL , 2014-2015 -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: oslo.db 4.2.1.dev4\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-07 02:02+0000\n" +"Project-Id-Version: oslo.db 4.6.1.dev19\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 04:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -33,9 +33,6 @@ "Spécifiez la colonne %s dans le paramètre col_name_col_instance. Ceci est " "obligatoire car la colonne a un type non pris en charge dans SQLite." -msgid "Sort key supplied was not valid." -msgstr "La clé de tri fournie n'était pas valide." - #, python-format msgid "" "Tables \"%s\" have non utf8 collation, please make sure all tables are " diff -Nru python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-error.pot python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-error.pot --- python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-error.pot 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-error.pot 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Translations template for oslo.db. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the oslo.db project. -# FIRST AUTHOR , 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.db 4.3.0\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-17 06:14+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.2.0\n" - -#: oslo_db/api.py:146 -msgid "DB error." -msgstr "" - -#: oslo_db/api.py:148 -msgid "DB exceeded retry limit." -msgstr "" - -#: oslo_db/concurrency.py:64 -msgid "'eventlet' is required for TpoolDbapiWrapper." -msgstr "" - -#: oslo_db/sqlalchemy/exc_filters.py:328 -#, python-format -msgid "DBAPIError exception wrapped from %s" -msgstr "" - -#: oslo_db/sqlalchemy/exc_filters.py:339 -msgid "DB exception wrapped." -msgstr "" - -#: oslo_db/sqlalchemy/test_migrations.py:272 -#, python-format -msgid "Failed to migrate to version %(ver)s on engine %(eng)s" -msgstr "" - -#: oslo_db/sqlalchemy/migration_cli/ext_migrate.py:61 -msgid "" -"Migration number for migrate plugin must be valid integer or empty, if " -"you want to downgrade to initial state" -msgstr "" - diff -Nru python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-info.pot python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-info.pot --- python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-info.pot 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-info.pot 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -# Translations template for oslo.db. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.db project. -# FIRST AUTHOR , 2015. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.db 2.6.1.dev18\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-26 06:13+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" - -#: oslo_db/sqlalchemy/provision.py:221 oslo_db/sqlalchemy/provision.py:232 -#, python-format -msgid "The %(dbapi)s backend is unavailable: %(err)s" -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:444 -#, python-format -msgid "Deleting duplicated row with id: %(id)s from table: %(table)s" -msgstr "" - diff -Nru python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-warning.pot python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-warning.pot --- python-oslo.db-4.7.0/oslo_db/locale/oslo_db-log-warning.pot 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/oslo_db-log-warning.pot 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -# Translations template for oslo.db. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.db project. -# FIRST AUTHOR , 2015. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.db 2.6.1.dev18\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-26 06:13+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" - -#: oslo_db/sqlalchemy/engines.py:253 -msgid "Unable to detect effective SQL mode" -msgstr "" - -#: oslo_db/sqlalchemy/engines.py:260 -#, python-format -msgid "MySQL SQL mode is '%s', consider enabling TRADITIONAL or STRICT_ALL_TABLES" -msgstr "" - -#: oslo_db/sqlalchemy/engines.py:328 -#, python-format -msgid "SQL connection failed. %s attempts left." -msgstr "" - -#: oslo_db/sqlalchemy/engines.py:353 -#, python-format -msgid "" -"Parent process %(orig)s forked (%(newproc)s) with an open database " -"connection, which is being discarded and recreated." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:107 -msgid "Id not in sort_keys; is sort_keys unique?" -msgstr "" - diff -Nru python-oslo.db-4.7.0/oslo_db/locale/oslo_db.pot python-oslo.db-4.17.0/oslo_db/locale/oslo_db.pot --- python-oslo.db-4.7.0/oslo_db/locale/oslo_db.pot 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/locale/oslo_db.pot 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -# Translations template for oslo.db. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.db project. -# FIRST AUTHOR , 2015. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.db 2.6.1.dev18\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-26 06:13+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" - -#: oslo_db/exception.py:150 -msgid "Invalid Parameter: Encoding directive wasn't provided." -msgstr "" - -#: oslo_db/exception.py:186 -msgid "Sort key supplied was not valid." -msgstr "" - -#: oslo_db/sqlalchemy/migration.py:72 -msgid "version should be an integer" -msgstr "" - -#: oslo_db/sqlalchemy/migration.py:112 -#, python-format -msgid "" -"Tables \"%s\" have non utf8 collation, please make sure all tables are " -"CHARSET=utf8" -msgstr "" - -#: oslo_db/sqlalchemy/migration.py:136 -msgid "" -"The database is not under version control, but has tables. Please stamp " -"the current version of the schema manually." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:143 -#, python-format -msgid "Unknown sort direction, must be one of: %s" -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:198 -#, python-format -msgid "" -"There is no `deleted` column in `%s` table. Project doesn't use soft-" -"deleted feature." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:212 -#, python-format -msgid "There is no `project_id` column in `%s` table." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:324 -msgid "model should be a subclass of ModelBase" -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:394 -#, python-format -msgid "" -"Please specify column %s in col_name_col_instance param. It is required " -"because column has unsupported type by SQLite." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:400 -#, python-format -msgid "" -"col_name_col_instance param has wrong type of column instance for column " -"%s It should be instance of sqlalchemy.Column." -msgstr "" - -#: oslo_db/sqlalchemy/utils.py:465 -msgid "Unsupported id columns type" -msgstr "" - diff -Nru python-oslo.db-4.7.0/oslo_db/options.py python-oslo.db-4.17.0/oslo_db/options.py --- python-oslo.db-4.7.0/oslo_db/options.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/options.py 2017-01-18 14:09:32.000000000 +0000 @@ -10,11 +10,15 @@ # License for the specific language governing permissions and limitations # under the License. +from debtcollector import removals from oslo_config import cfg database_opts = [ cfg.StrOpt('sqlite_db', + deprecated_for_removal=True, + deprecated_reason='Should use config option connection or ' + 'slave_connection to connect the database.', deprecated_group='DEFAULT', default='oslo.sqlite', help='The file name to use with SQLite.'), @@ -66,12 +70,13 @@ help='Minimum number of SQL connections to keep open in a ' 'pool.'), cfg.IntOpt('max_pool_size', + default=5, deprecated_opts=[cfg.DeprecatedOpt('sql_max_pool_size', group='DEFAULT'), cfg.DeprecatedOpt('sql_max_pool_size', group='DATABASE')], help='Maximum number of SQL connections to keep open in a ' - 'pool.'), + 'pool. Setting a value of 0 indicates no limit.'), cfg.IntOpt('max_retries', default=10, deprecated_opts=[cfg.DeprecatedOpt('sql_max_retries', @@ -98,6 +103,7 @@ 'SQLAlchemy.'), cfg.IntOpt('connection_debug', default=0, + min=0, max=100, deprecated_opts=[cfg.DeprecatedOpt('sql_connection_debug', group='DEFAULT')], help='Verbosity of SQL debugging information: 0=None, ' @@ -137,6 +143,9 @@ ] +@removals.removed_kwarg("sqlite_db", + "Config option sqlite_db is deprecated for removal," + "please use option `connection`.") def set_defaults(conf, connection=None, sqlite_db=None, max_pool_size=None, max_overflow=None, pool_timeout=None): @@ -160,13 +169,12 @@ :type sqlite_db: str :keyword max_pool_size: maximum connections pool size. The size of the pool - to be maintained, defaults to 5, will be used if value of the parameter is - `None`. This is the largest number of connections that will be kept - persistently in the pool. Note that the pool begins with no connections; - once this number of connections is requested, that number of connections - will remain. + to be maintained, defaults to 5. This is the largest number of connections + that will be kept persistently in the pool. Note that the pool begins with + no connections; once this number of connections is requested, that number + of connections will remain. :type max_pool_size: int - :default max_pool_size: None + :default max_pool_size: 5 :keyword max_overflow: The maximum overflow size of the pool. When the number of checked-out connections reaches the size set in pool_size, diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/handle_error.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/handle_error.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/handle_error.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/handle_error.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,341 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Provide forwards compatibility for the handle_error event. - -See the "handle_error" event at -http://docs.sqlalchemy.org/en/rel_0_9/core/events.html. - - -""" -import sys - -import six -from sqlalchemy.engine import base as engine_base -from sqlalchemy.engine import Engine -from sqlalchemy import event -from sqlalchemy import exc as sqla_exc - -from oslo_db.sqlalchemy.compat import utils - - -def handle_error(engine, listener): - """Add a handle_error listener for the given :class:`.Engine`. - - This listener uses the SQLAlchemy - :meth:`sqlalchemy.event.ConnectionEvents.handle_error` - event. - - """ - if utils.sqla_100: - event.listen(engine, "handle_error", listener) - return - - assert isinstance(engine, Engine), \ - "engine argument must be an Engine instance, not a Connection" - - assert utils.sqla_097 - - _rework_connect_and_revalidate_for_events(engine) - - # ctx.engine added per - # https://bitbucket.org/zzzeek/sqlalchemy/issue/3266/ - def wrap_listener(ctx): - if isinstance(ctx, engine_base.ExceptionContextImpl): - ctx.engine = ctx.connection.engine - return listener(ctx) - event.listen(engine, "handle_error", wrap_listener) - - -def _rework_connect_and_revalidate_for_events(engine): - """Patch the _revalidate_connection() system on Connection. - - This applies 1.0's _revalidate_connection() approach into an 0.9 - version of SQLAlchemy, and consists of three steps: - - 1. wrap the pool._creator function, which in 0.9 has a local - call to sqlalchemy.exc.DBAPIError.instance(), so that this exception is - again unwrapped back to the original DBAPI-specific Error, then raise - that. This is essentially the same as if the dbapi.connect() isn't - wrapped in the first place, which is how SQLAlchemy 1.0 now functions. - - 2. patch the Engine object's raw_connection() method. In SQLAlchemy 1.0, - this is now where the error wrapping occurs when a pool connect attempt - is made. Here, when raw_connection() is called without a hosting - Connection, we send exception raises to - _handle_dbapi_exception_noconnection(), here copied from SQLAlchemy - 1.0, which is an alternate version of Connection._handle_dbapi_exception() - tailored for an initial connect failure when there is no - Connection object being dealt with. This allows the error handler - events to be called. - - 3. patch the Connection class to follow 1.0's behavior for - _revalidate_connection(); here, the call to engine.raw_connection() - will pass the raised error to Connection._handle_dbapi_exception(), - again allowing error handler events to be called. - - """ - - _orig_connect = engine.pool._creator - - def connect(): - try: - return _orig_connect() - except sqla_exc.DBAPIError as err: - original_exception = err.orig - raise original_exception - engine.pool._creator = connect - - self = engine - - def contextual_connect(close_with_result=False, **kwargs): - return self._connection_cls( - self, - self._wrap_pool_connect(self.pool.connect, None), - close_with_result=close_with_result, - **kwargs) - - def _wrap_pool_connect(fn, connection): - dialect = self.dialect - try: - return fn() - except dialect.dbapi.Error as e: - if connection is None: - _handle_dbapi_exception_noconnection( - e, dialect, self) - else: - six.reraise(*sys.exc_info()) - - def raw_connection(_connection=None): - return self._wrap_pool_connect( - self.pool.unique_connection, _connection) - - engine.contextual_connect = contextual_connect - engine._wrap_pool_connect = _wrap_pool_connect - engine.raw_connection = raw_connection - - class Connection(engine._connection_cls): - - @property - def connection(self): - "The underlying DB-API connection managed by this Connection." - try: - return self.__connection - except AttributeError: - try: - return self._revalidate_connection() - except Exception as e: - self._handle_dbapi_exception(e, None, None, None, None) - - def _handle_dbapi_exception(self, - e, - statement, - parameters, - cursor, - context): - if self.invalidated: - # 0.9's _handle_dbapi_exception() can't handle - # a Connection that is invalidated already, meaning - # its "__connection" attribute is not set. So if we are - # in that case, call our "no connection" invalidator. - # this is fine as we are only supporting handle_error listeners - # that are applied at the engine level. - _handle_dbapi_exception_noconnection( - e, self.dialect, self.engine) - else: - super(Connection, self)._handle_dbapi_exception( - e, statement, parameters, cursor, context) - - def _revalidate_connection(self): - if self._Connection__can_reconnect and self._Connection__invalid: - if self._Connection__transaction is not None: - raise sqla_exc.InvalidRequestError( - "Can't reconnect until invalid " - "transaction is rolled back") - self._Connection__connection = self.engine.raw_connection( - _connection=self) - self._Connection__invalid = False - return self._Connection__connection - raise sqla_exc.ResourceClosedError("This Connection is closed") - - engine._connection_cls = Connection - - -def _handle_dbapi_exception_noconnection(e, dialect, engine): - - exc_info = sys.exc_info() - - is_disconnect = dialect.is_disconnect(e, None, None) - - should_wrap = isinstance(e, dialect.dbapi.Error) - - if should_wrap: - sqlalchemy_exception = sqla_exc.DBAPIError.instance( - None, - None, - e, - dialect.dbapi.Error, - connection_invalidated=is_disconnect) - else: - sqlalchemy_exception = None - - newraise = None - - ctx = ExceptionContextImpl( - e, sqlalchemy_exception, engine, None, None, None, - None, None, is_disconnect) - - if hasattr(engine, '_oslo_handle_error_events'): - fns = engine._oslo_handle_error_events - else: - fns = engine.dispatch.handle_error - for fn in fns: - try: - # handler returns an exception; - # call next handler in a chain - per_fn = fn(ctx) - if per_fn is not None: - ctx.chained_exception = newraise = per_fn - except Exception as _raised: - # handler raises an exception - stop processing - newraise = _raised - break - - if sqlalchemy_exception and \ - is_disconnect != ctx.is_disconnect: - sqlalchemy_exception.connection_invalidated = \ - is_disconnect = ctx.is_disconnect - - if newraise: - six.reraise(type(newraise), newraise, exc_info[2]) - elif should_wrap: - six.reraise( - type(sqlalchemy_exception), sqlalchemy_exception, exc_info[2]) - else: - six.reraise(*exc_info) - - -class ExceptionContextImpl(object): - """Encapsulate information about an error condition in progress. - - This is for forwards compatibility with the - ExceptionContext interface introduced in SQLAlchemy 0.9.7. - - It also provides for the "engine" argument added in SQLAlchemy 1.0.0. - - """ - - def __init__(self, exception, sqlalchemy_exception, - engine, connection, cursor, statement, parameters, - context, is_disconnect): - self.engine = engine - self.connection = connection - self.sqlalchemy_exception = sqlalchemy_exception - self.original_exception = exception - self.execution_context = context - self.statement = statement - self.parameters = parameters - self.is_disconnect = is_disconnect - - connection = None - """The :class:`.Connection` in use during the exception. - - This member is present, except in the case of a failure when - first connecting. - - - """ - - engine = None - """The :class:`.Engine` in use during the exception. - - This member should always be present, even in the case of a failure - when first connecting. - - """ - - cursor = None - """The DBAPI cursor object. - - May be None. - - """ - - statement = None - """String SQL statement that was emitted directly to the DBAPI. - - May be None. - - """ - - parameters = None - """Parameter collection that was emitted directly to the DBAPI. - - May be None. - - """ - - original_exception = None - """The exception object which was caught. - - This member is always present. - - """ - - sqlalchemy_exception = None - """The :class:`sqlalchemy.exc.StatementError` which wraps the original, - and will be raised if exception handling is not circumvented by the event. - - May be None, as not all exception types are wrapped by SQLAlchemy. - For DBAPI-level exceptions that subclass the dbapi's Error class, this - field will always be present. - - """ - - chained_exception = None - """The exception that was returned by the previous handler in the - exception chain, if any. - - If present, this exception will be the one ultimately raised by - SQLAlchemy unless a subsequent handler replaces it. - - May be None. - - """ - - execution_context = None - """The :class:`.ExecutionContext` corresponding to the execution - operation in progress. - - This is present for statement execution operations, but not for - operations such as transaction begin/end. It also is not present when - the exception was raised before the :class:`.ExecutionContext` - could be constructed. - - Note that the :attr:`.ExceptionContext.statement` and - :attr:`.ExceptionContext.parameters` members may represent a - different value than that of the :class:`.ExecutionContext`, - potentially in the case where a - :meth:`.ConnectionEvents.before_cursor_execute` event or similar - modified the statement/parameters to be sent. - - May be None. - - """ - - is_disconnect = None - """Represent whether the exception as occurred represents a "disconnect" - condition. - - This flag will always be True or False within the scope of the - :meth:`.ConnectionEvents.handle_error` handler. - - """ diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/__init__.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/__init__.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/__init__.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/__init__.py 2017-01-18 14:09:32.000000000 +0000 @@ -1,25 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""compatiblity extensions for SQLAlchemy versions. - -Elements within this module provide SQLAlchemy features that have been -added at some point but for which oslo.db provides a compatible versions -for previous SQLAlchemy versions. - -""" -from oslo_db.sqlalchemy.compat import handle_error as _h_err - -# trying to get: "from oslo_db.sqlalchemy import compat; compat.handle_error" -# flake8 won't let me import handle_error directly -handle_error = _h_err.handle_error - -__all__ = ['handle_error'] diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/utils.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/utils.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/compat/utils.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/compat/utils.py 2017-01-18 14:09:32.000000000 +0000 @@ -19,6 +19,7 @@ for num in sqlalchemy.__version__.split(".") ) +sqla_110 = SQLA_VERSION >= (1, 1, 0) sqla_100 = SQLA_VERSION >= (1, 0, 0) sqla_097 = SQLA_VERSION >= (0, 9, 7) sqla_094 = SQLA_VERSION >= (0, 9, 4) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/enginefacade.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/enginefacade.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/enginefacade.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/enginefacade.py 2017-01-18 14:09:32.000000000 +0000 @@ -13,6 +13,7 @@ import contextlib import functools +import inspect import operator import threading import warnings @@ -141,7 +142,10 @@ 'connection_trace': _Default(False), 'max_retries': _Default(10), 'retry_interval': _Default(10), - 'thread_checkin': _Default(True) + 'thread_checkin': _Default(True), + 'json_serializer': _Default(None), + 'json_deserializer': _Default(None), + 'logging_name': _Default(None) } self._maker_cfg = { 'expire_on_commit': _Default(False), @@ -151,7 +155,8 @@ 'rollback_reader_sessions': False, } self._facade_cfg = { - 'synchronous_reader': True + 'synchronous_reader': True, + 'on_engine_create': [], } # other options that are defined in oslo.db.options.database_opts @@ -257,6 +262,46 @@ return self._legacy_facade + def get_writer_engine(self): + """Return the writer engine for this factory. + + Implies start. + + """ + if not self._started: + self._start() + return self._writer_engine + + def get_reader_engine(self): + """Return the reader engine for this factory. + + Implies start. + + """ + if not self._started: + self._start() + return self._reader_engine + + def get_writer_maker(self): + """Return the writer sessionmaker for this factory. + + Implies start. + + """ + if not self._started: + self._start() + return self._writer_maker + + def get_reader_maker(self): + """Return the reader sessionmaker for this factory. + + Implies start. + + """ + if not self._started: + self._start() + return self._reader_maker + def _create_connection(self, mode): if not self._started: self._start() @@ -282,6 +327,15 @@ else: return self._writer_maker(**kw) + def _create_factory_copy(self): + factory = _TransactionFactory() + factory._url_cfg.update(self._url_cfg) + factory._engine_cfg.update(self._engine_cfg) + factory._maker_cfg.update(self._maker_cfg) + factory._transaction_ctx_cfg.update(self._transaction_ctx_cfg) + factory._facade_cfg.update(self._facade_cfg) + return factory + def _args_for_conf(self, default_cfg, conf): if conf is None: return dict( @@ -303,7 +357,19 @@ return self._args_for_conf(self._engine_cfg, conf) def _maker_args_for_conf(self, conf): - return self._args_for_conf(self._maker_cfg, conf) + maker_args = self._args_for_conf(self._maker_cfg, conf) + maker_args['autocommit'] = maker_args.pop('__autocommit') + return maker_args + + def dispose_pool(self): + """Call engine.pool.dispose() on underlying Engine objects.""" + with self._start_lock: + if not self._started: + return + + self._writer_engine.pool.dispose() + if self._reader_engine is not self._writer_engine: + self._reader_engine.pool.dispose() def _start(self, conf=False, connection=None, slave_connection=None): with self._start_lock: @@ -330,7 +396,6 @@ url_args['slave_connection'] = slave_connection engine_args = self._engine_args_for_conf(conf) maker_args = self._maker_args_for_conf(conf) - maker_args['autocommit'] = maker_args.pop('__autocommit') self._writer_engine, self._writer_maker = \ self._setup_for_connection( @@ -360,6 +425,8 @@ "No sql_connection parameter is established") engine = engines.create_engine( sql_connection=sql_connection, **engine_kwargs) + for hook in self._facade_cfg['on_engine_create']: + hook(engine) sessionmaker = orm.get_maker(engine=engine, **maker_kwargs) return engine, sessionmaker @@ -516,7 +583,7 @@ session.commit() elif self.rollback_reader_sessions: session.rollback() - # In the absense of calling session.rollback(), + # In the absence of calling session.rollback(), # the next call is session.close(). This releases all # objects from the session into the detached state, and # releases the connection as well; the connection when returned @@ -624,6 +691,10 @@ """ self._factory.configure(**kw) + def append_on_engine_create(self, fn): + """Append a listener function to _facade_cfg["on_engine_create"]""" + self._factory._facade_cfg['on_engine_create'].append(fn) + def get_legacy_facade(self): """Return a :class:`.LegacyEngineFacade` for factory from this context. @@ -635,6 +706,115 @@ return self._factory.get_legacy_facade() + def get_engine(self): + """Return the Engine in use. + + This will be based on the state being WRITER or READER. + + This implies a start operation. + + """ + if self._mode is _WRITER: + return self._factory.get_writer_engine() + elif self._mode is _READER: + return self._factory.get_reader_engine() + else: + raise ValueError("mode should be WRITER or READER") + + def get_sessionmaker(self): + """Return the sessionmaker in use. + + This will be based on the state being WRITER or READER. + + This implies a start operation. + + """ + if self._mode is _WRITER: + return self._factory.get_writer_maker() + elif self._mode is _READER: + return self._factory.get_reader_maker() + else: + raise ValueError("mode should be WRITER or READER") + + def dispose_pool(self): + """Call engine.pool.dispose() on underlying Engine objects.""" + self._factory.dispose_pool() + + def make_new_manager(self): + """Create a new, independent _TransactionContextManager from this one. + + Copies the underlying _TransactionFactory to a new one, so that + it can be further configured with new options. + + Used for test environments where the application-wide + _TransactionContextManager may be used as a factory for test-local + managers. + + """ + new = self._clone() + new._root = new + new._root_factory = self._root_factory._create_factory_copy() + assert not new._factory._started + return new + + def patch_factory(self, factory_or_manager): + """Patch a _TransactionFactory into this manager. + + Replaces this manager's factory with the given one, and returns + a callable that will reset the factory back to what we + started with. + + Only works for root factories. Is intended for test suites + that need to patch in alternate database configurations. + + The given argument may be a _TransactionContextManager or a + _TransactionFactory. + + """ + + if isinstance(factory_or_manager, _TransactionContextManager): + factory = factory_or_manager._factory + elif isinstance(factory_or_manager, _TransactionFactory): + factory = factory_or_manager + else: + raise ValueError( + "_TransactionContextManager or " + "_TransactionFactory expected.") + assert self._root is self + existing_factory = self._root_factory + self._root_factory = factory + + def reset(): + self._root_factory = existing_factory + + return reset + + def patch_engine(self, engine): + """Patch an Engine into this manager. + + Replaces this manager's factory with a _TestTransactionFactory + that will use the given Engine, and returns + a callable that will reset the factory back to what we + started with. + + Only works for root factories. Is intended for test suites + that need to patch in alternate database configurations. + + """ + + existing_factory = self._factory + maker = existing_factory._writer_maker + maker_kwargs = existing_factory._maker_args_for_conf(cfg.CONF) + maker = orm.get_maker(engine=engine, **maker_kwargs) + + factory = _TestTransactionFactory( + engine, maker, + apply_global=False, + synchronous_reader=existing_factory. + _facade_cfg['synchronous_reader'] + ) + return self.patch_factory(factory) + @property def replace(self): """Modifier to replace the global transaction factory with this one.""" @@ -700,10 +880,15 @@ def __call__(self, fn): """Decorate a function.""" + argspec = inspect.getargspec(fn) + if argspec.args[0] == 'self' or argspec.args[0] == 'cls': + context_index = 1 + else: + context_index = 0 @functools.wraps(fn) def wrapper(*args, **kwargs): - context = args[0] + context = args[context_index] with self._transaction_scope(context): return fn(*args, **kwargs) @@ -1006,7 +1191,7 @@ (defaults to False) :type use_slave: bool - Keyword arugments will be passed to a sessionmaker instance as is (if + Keyword arguments will be passed to a sessionmaker instance as is (if passed, they will override the ones used when the sessionmaker instance was created). See SQLAlchemy Session docs for details. diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/engines.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/engines.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/engines.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/engines.py 2017-01-18 14:09:32.000000000 +0000 @@ -93,7 +93,7 @@ """ if connection_debug >= 0: logger = logging.getLogger('sqlalchemy.engine') - if connection_debug >= 100: + if connection_debug == 100: logger.setLevel(logging.DEBUG) elif connection_debug >= 50: logger.setLevel(logging.INFO) @@ -106,7 +106,9 @@ connection_debug=0, max_pool_size=None, max_overflow=None, pool_timeout=None, sqlite_synchronous=True, connection_trace=False, max_retries=10, retry_interval=10, - thread_checkin=True, logging_name=None): + thread_checkin=True, logging_name=None, + json_serializer=None, + json_deserializer=None): """Return a new SQLAlchemy engine.""" url = sqlalchemy.engine.url.make_url(sql_connection) @@ -122,10 +124,11 @@ _init_connection_args( url, engine_args, - sqlite_fk=sqlite_fk, max_pool_size=max_pool_size, max_overflow=max_overflow, - pool_timeout=pool_timeout + pool_timeout=pool_timeout, + json_serializer=json_serializer, + json_deserializer=json_deserializer, ) engine = sqlalchemy.create_engine(url, **engine_args) @@ -187,6 +190,8 @@ # it's supported for PostgreSQL 8.*. More details at: # http://docs.sqlalchemy.org/en/rel_0_9/dialects/postgresql.html engine_args['client_encoding'] = 'utf8' + engine_args['json_serializer'] = kw.get('json_serializer') + engine_args['json_deserializer'] = kw.get('json_deserializer') @_init_connection_args.dispatch_for("mysql") diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/exc_filters.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/exc_filters.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/exc_filters.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/exc_filters.py 2017-01-18 14:09:32.000000000 +0000 @@ -14,12 +14,13 @@ import collections import logging import re +import sys +from sqlalchemy import event from sqlalchemy import exc as sqla_exc from oslo_db._i18n import _LE from oslo_db import exception -from oslo_db.sqlalchemy import compat LOG = logging.getLogger(__name__) @@ -58,9 +59,11 @@ # as well as sqlalchemy.exc.DBAPIError, as SQLAlchemy will reraise it # as this until issue #3075 is fixed. @filters("mysql", sqla_exc.OperationalError, r"^.*\b1213\b.*Deadlock found.*") -@filters("mysql", sqla_exc.OperationalError, +@filters("mysql", sqla_exc.DatabaseError, r"^.*\b1205\b.*Lock wait timeout exceeded.*") @filters("mysql", sqla_exc.InternalError, r"^.*\b1213\b.*Deadlock found.*") +@filters("mysql", sqla_exc.InternalError, + r"^.*\b1213\b.*detected deadlock/conflict.*") @filters("postgresql", sqla_exc.OperationalError, r"^.*deadlock detected.*") @filters("postgresql", sqla_exc.DBAPIError, r"^.*deadlock detected.*") @filters("ibm_db_sa", sqla_exc.DBAPIError, r"^.*SQL0911N.*") @@ -91,11 +94,11 @@ @filters("mysql", sqla_exc.IntegrityError, - r"^.*\b1062\b.*Duplicate entry '(?P.+)'" + r"^.*\b1062\b.*Duplicate entry '(?P.*)'" r" for key '(?P[^']+)'.*$") # NOTE(jd) For binary types @filters("mysql", sqla_exc.IntegrityError, - r"^.*\b1062\b.*Duplicate entry \\'(?P.+)\\'" + r"^.*\b1062\b.*Duplicate entry \\'(?P.*)\\'" r" for key \\'(?P.+)\\'.*$") # NOTE(pkholkin): the first regex is suitable only for PostgreSQL 9.x versions # the second regex is suitable for PostgreSQL 8.x versions @@ -196,7 +199,7 @@ "is (not present in|still referenced from) table " "\"(?P[^\"]+)\".") @filters("mysql", sqla_exc.IntegrityError, - r".* u?'Cannot (add|delete) or update a (child|parent) row: " + r".*Cannot (add|delete) or update a (child|parent) row: " 'a foreign key constraint fails \([`"].+[`"]\.[`"](?P.+)[`"], ' 'CONSTRAINT [`"](?P.+)[`"] FOREIGN KEY ' '\([`"](?P.+)[`"]\) REFERENCES [`"](?P.+)[`"] ') @@ -244,6 +247,69 @@ raise exception.DBConstraintError(table, check_name, integrity_error) +@filters("postgresql", sqla_exc.ProgrammingError, + r".* constraint \"(?P.+)\" " + "of relation " + "\"(?P.+)\" does not exist") +@filters("mysql", sqla_exc.InternalError, + r".*1091,.*Can't DROP '(?P.+)'; " + "check that column/key exists") +@filters("mysql", sqla_exc.OperationalError, + r".*1091,.*Can't DROP '(?P.+)'; " + "check that column/key exists") +@filters("mysql", sqla_exc.InternalError, + r".*1025,.*Error on rename of '.+/(?P.+)' to ") +def _check_constraint_non_existing( + programming_error, match, engine_name, is_disconnect): + """Filter for constraint non existing errors.""" + + try: + relation = match.group("relation") + except IndexError: + relation = None + + try: + constraint = match.group("constraint") + except IndexError: + constraint = None + + raise exception.DBNonExistentConstraint(relation, + constraint, + programming_error) + + +@filters("sqlite", sqla_exc.OperationalError, + r".* no such table: (?P
.+)") +@filters("mysql", sqla_exc.InternalError, + r".*1051,.*Unknown table '(.+\.)?(?P
.+)'\"") +@filters("mysql", sqla_exc.OperationalError, + r".*1051,.*Unknown table '(.+\.)?(?P
.+)'\"") +@filters("postgresql", sqla_exc.ProgrammingError, + r".* table \"(?P
.+)\" does not exist") +def _check_table_non_existing( + programming_error, match, engine_name, is_disconnect): + """Filter for table non existing errors.""" + raise exception.DBNonExistentTable(match.group("table"), programming_error) + + +@filters("mysql", sqla_exc.InternalError, + r".*1049,.*Unknown database '(?P.+)'\"") +@filters("mysql", sqla_exc.OperationalError, + r".*1049,.*Unknown database '(?P.+)'\"") +@filters("postgresql", sqla_exc.OperationalError, + r".*database \"(?P.+)\" does not exist") +@filters("sqlite", sqla_exc.OperationalError, + ".*unable to open database file.*") +def _check_database_non_existing( + error, match, engine_name, is_disconnect): + try: + database = match.group("database") + except IndexError: + database = None + + raise exception.DBNonExistentDatabase(database, error) + + @filters("ibm_db_sa", sqla_exc.IntegrityError, r"^.*SQL0803N.*$") def _db2_dupe_key_error(integrity_error, match, engine_name, is_disconnect): """Filter for DB2 duplicate key errors. @@ -282,12 +348,24 @@ r".*1264.*Out of range value for column.*") @filters("mysql", sqla_exc.InternalError, r"^.*1366.*Incorrect string value:*") +@filters("sqlite", sqla_exc.ProgrammingError, + r"(?i).*You must not use 8-bit bytestrings*") +@filters("mysql", sqla_exc.DataError, + r".*1406.*Data too long for column.*") def _raise_data_error(error, match, engine_name, is_disconnect): """Raise DBDataError exception for different data errors.""" raise exception.DBDataError(error) +@filters("mysql", sqla_exc.OperationalError, + r".*\(1305,\s+\'SAVEPOINT\s+(.+)\s+does not exist\'\)") +def _raise_savepoints_as_dberrors(error, match, engine_name, is_disconnect): + # NOTE(rpodolyaka): this is a special case of an OperationalError that used + # to be an InternalError. It's expected to be wrapped into oslo.db error. + raise exception.DBError(error) + + @filters("*", sqla_exc.OperationalError, r".*") def _raise_operational_errors_directly_filter(operational_error, match, engine_name, @@ -308,6 +386,8 @@ @filters("mysql", sqla_exc.OperationalError, r".*\(.*(?:2002|2003|2006|2013|1047)") # noqa +@filters("mysql", sqla_exc.InternalError, r".*\(.*(?:1927)") # noqa +@filters("mysql", sqla_exc.InternalError, r".*Packet sequence number wrong") # noqa @filters("postgresql", sqla_exc.OperationalError, r".*could not connect to server") # noqa @filters("ibm_db_sa", sqla_exc.OperationalError, r".*(?:30081)") def _is_db_connection_error(operational_error, match, engine_name, @@ -341,6 +421,8 @@ LOG.exception(_LE('DB exception wrapped.')) raise exception.DBError(error) +ROLLBACK_CAUSE_KEY = 'oslo.db.sp_rollback_cause' + def handler(context): """Iterate through available filters and invoke those which match. @@ -374,13 +456,52 @@ match, context.engine.dialect.name, context.is_disconnect) - except exception.DBConnectionError: - context.is_disconnect = True + except exception.DBError as dbe: + if ( + context.connection is not None and + not context.connection.closed and + not context.connection.invalidated and + ROLLBACK_CAUSE_KEY + in context.connection.info + ): + dbe.cause = \ + context.connection.info.pop( + ROLLBACK_CAUSE_KEY) + + if isinstance( + dbe, exception.DBConnectionError): + context.is_disconnect = True raise def register_engine(engine): - compat.handle_error(engine, handler) + event.listen(engine, "handle_error", handler) + + @event.listens_for(engine, "rollback_savepoint") + def rollback_savepoint(conn, name, context): + exc_info = sys.exc_info() + if exc_info[1]: + conn.info[ROLLBACK_CAUSE_KEY] = exc_info[1] + # NOTE(zzzeek) this eliminates a reference cycle between tracebacks + # that would occur in Python 3 only, which has been shown to occur if + # this function were in fact part of the traceback. That's not the + # case here however this is left as a defensive measure. + del exc_info + + # try to clear the "cause" ASAP outside of savepoints, + # by grabbing the end of transaction events... + @event.listens_for(engine, "rollback") + @event.listens_for(engine, "commit") + def pop_exc_tx(conn): + conn.info.pop(ROLLBACK_CAUSE_KEY, None) + + # .. as well as connection pool checkin (just in case). + # the .info dictionary lasts as long as the DBAPI connection itself + # and is cleared out when the connection is recycled or closed + # due to invalidate etc. + @event.listens_for(engine, "checkin") + def pop_exc_checkin(dbapi_conn, connection_record): + connection_record.info.pop(ROLLBACK_CAUSE_KEY, None) def handle_connect_error(engine): diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/migration_cli/manager.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/migration_cli/manager.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/migration_cli/manager.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/migration_cli/manager.py 2017-01-18 14:09:32.000000000 +0000 @@ -57,7 +57,7 @@ # revision=None is a special case meaning latest revision. rev_in_plugins = [p.has_revision(revision) for p in self._plugins] if not any(rev_in_plugins) and revision is not None: - raise exception.DbMigrationError('Revision does not exist') + raise exception.DBMigrationError('Revision does not exist') results = [] for plugin, has_revision in zip(self._plugins, rev_in_plugins): @@ -75,7 +75,7 @@ # revision=None is a special case meaning initial revision. rev_in_plugins = [p.has_revision(revision) for p in self._plugins] if not any(rev_in_plugins) and revision is not None: - raise exception.DbMigrationError('Revision does not exist') + raise exception.DBMigrationError('Revision does not exist') # downgrading should be performed in reversed order results = [] diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/migration.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/migration.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/migration.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/migration.py 2017-01-18 14:09:32.000000000 +0000 @@ -68,8 +68,7 @@ try: version = int(version) except ValueError: - raise exception.DbMigrationError( - message=_("version should be an integer")) + raise exception.DBMigrationError(_("version should be an integer")) current_version = db_version(engine, abs_path, init_version) repository = _find_migrate_repo(abs_path) @@ -119,7 +118,7 @@ :param engine: SQLAlchemy engine instance for a given database :param abs_path: Absolute path to migrate repository - :param version: Initial database version + :param init_version: Initial database version """ repository = _find_migrate_repo(abs_path) try: @@ -132,11 +131,10 @@ db_version_control(engine, abs_path, version=init_version) return versioning_api.db_version(engine, repository) else: - raise exception.DbMigrationError( - message=_( - "The database is not under version control, but has " - "tables. Please stamp the current version of the schema " - "manually.")) + raise exception.DBMigrationError( + _("The database is not under version control, but has " + "tables. Please stamp the current version of the schema " + "manually.")) def db_version_control(engine, abs_path, version=None): @@ -150,7 +148,14 @@ :param version: Initial database version """ repository = _find_migrate_repo(abs_path) - versioning_api.version_control(engine, repository, version) + + try: + versioning_api.version_control(engine, repository, version) + except versioning_exceptions.InvalidVersionError as ex: + raise exception.DBMigrationError("Invalid version : %s" % ex) + except versioning_exceptions.DatabaseAlreadyControlledError: + raise exception.DBMigrationError("Database is already controlled.") + return version @@ -160,5 +165,5 @@ :param abs_path: Absolute path to migrate repository """ if not os.path.exists(abs_path): - raise exception.DbMigrationError("Path %s not found" % abs_path) + raise exception.DBMigrationError("Path %s not found" % abs_path) return Repository(abs_path) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/models.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/models.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/models.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/models.py 2017-01-18 14:09:32.000000000 +0000 @@ -23,10 +23,12 @@ import six from oslo_utils import timeutils -from sqlalchemy import Column, Integer +from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy.orm import object_mapper +from oslo_db.sqlalchemy import types + class ModelBase(six.Iterator): """Base class for models.""" @@ -139,7 +141,7 @@ class SoftDeleteMixin(object): deleted_at = Column(DateTime) - deleted = Column(Integer, default=0) + deleted = Column(types.SoftDeleteInteger, default=0) def soft_delete(self, session): """Mark this object as deleted.""" diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/provision.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/provision.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/provision.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/provision.py 2017-01-18 14:09:32.000000000 +0000 @@ -1,3 +1,4 @@ +# Copyright 2014 Red Hat # Copyright 2013 Mirantis.inc # All Rights Reserved. # @@ -16,6 +17,7 @@ """Provision test environment for specific DB backends""" import abc +import debtcollector import logging import os import random @@ -31,7 +33,7 @@ from oslo_db._i18n import _LI from oslo_db import exception -from oslo_db.sqlalchemy.compat import utils as compat_utils +from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import session from oslo_db.sqlalchemy import utils @@ -39,52 +41,127 @@ class ProvisionedDatabase(object): - pass + """Represents a database engine pointing to a DB ready to run tests. + + backend: an instance of :class:`.Backend` + + enginefacade: an instance of :class:`._TransactionFactory` + + engine: a SQLAlchemy :class:`.Engine` + + db_token: if provision_new_database were used, this is the randomly + generated name of the database. Note that with SQLite memory + connections, this token is ignored. For a database that + wasn't actually created, will be None. + + """ + + __slots__ = 'backend', 'enginefacade', 'engine', 'db_token' + + def __init__(self, backend, enginefacade, engine, db_token): + self.backend = backend + self.enginefacade = enginefacade + self.engine = engine + self.db_token = db_token + + +class Schema(object): + """"Represents a database schema that has or will be populated. + + This is a marker object as required by testresources but otherwise + serves no purpose. + + """ + __slots__ = 'database', class BackendResource(testresources.TestResourceManager): - def __init__(self, database_type): + def __init__(self, database_type, ad_hoc_url=None): super(BackendResource, self).__init__() self.database_type = database_type self.backend = Backend.backend_for_database_type(self.database_type) + self.ad_hoc_url = ad_hoc_url + if ad_hoc_url is None: + self.backend = Backend.backend_for_database_type( + self.database_type) + else: + self.backend = Backend(self.database_type, ad_hoc_url) + self.backend._verify() def make(self, dependency_resources): return self.backend + def clean(self, resource): + self.backend._dispose() + def isDirty(self): return False class DatabaseResource(testresources.TestResourceManager): + """Database resource which connects and disconnects to a URL. + + For SQLite, this means the database is created implicitly, as a result + of SQLite's usual behavior. If the database is a file-based URL, + it will remain after the resource has been torn down. + + For all other kinds of databases, the resource indicates to connect + and disconnect from that database. + + """ - def __init__(self, database_type): + def __init__(self, database_type, _enginefacade=None, + provision_new_database=True, ad_hoc_url=None): super(DatabaseResource, self).__init__() self.database_type = database_type + self.provision_new_database = provision_new_database + + # NOTE(zzzeek) the _enginefacade is an optional argument + # here in order to accomodate Neutron's current direct use + # of the DatabaseResource object. Within oslo_db's use, + # the "enginefacade" will always be passed in from the + # test and/or fixture. + if _enginefacade: + self._enginefacade = _enginefacade + else: + self._enginefacade = enginefacade._context_manager self.resources = [ - ('backend', BackendResource(database_type)) + ('backend', BackendResource(database_type, ad_hoc_url)) ] def make(self, dependency_resources): - dependency_resources['db_token'] = db_token = _random_ident() backend = dependency_resources['backend'] - LOG.info( - "CREATE BACKEND %s TOKEN %s", backend.engine.url, db_token) - backend.create_named_database(db_token, conditional=True) - dependency_resources['engine'] = \ - backend.provisioned_engine(db_token) - return ProvisionedDatabase() + _enginefacade = self._enginefacade.make_new_manager() + + if self.provision_new_database: + db_token = _random_ident() + url = backend.provisioned_database_url(db_token) + LOG.info( + "CREATE BACKEND %s TOKEN %s", backend.engine.url, db_token) + backend.create_named_database(db_token, conditional=True) + else: + db_token = None + url = backend.url + + _enginefacade.configure( + logging_name="%s@%s" % (self.database_type, db_token)) + + _enginefacade._factory._start(connection=url) + engine = _enginefacade._factory._writer_engine + return ProvisionedDatabase(backend, _enginefacade, engine, db_token) def clean(self, resource): - resource.engine.dispose() - LOG.info( - "DROP BACKEND %s TOKEN %s", - resource.backend.engine, resource.db_token) - resource.backend.drop_named_database(resource.db_token) + if self.provision_new_database: + LOG.info( + "DROP BACKEND %s TOKEN %s", + resource.backend.engine, resource.db_token) + resource.backend.drop_named_database(resource.db_token) def isDirty(self): return False +@debtcollector.removals.removed_class("TransactionResource") class TransactionResource(testresources.TestResourceManager): def __init__(self, database_resource, schema_resource): @@ -105,10 +182,6 @@ return True -class Schema(object): - pass - - class SchemaResource(testresources.TestResourceManager): def __init__(self, database_resource, generate_schema, teardown=False): @@ -158,7 +231,6 @@ self.engine = None self.impl = BackendImpl.impl(database_type) self.current_dbs = set() - Backend.backends_by_database_type[database_type] = self @classmethod def backend_for_database_type(cls, database_type): @@ -168,7 +240,8 @@ try: backend = cls.backends_by_database_type[database_type] except KeyError: - raise exception.BackendNotAvailable(database_type) + raise exception.BackendNotAvailable( + "Backend '%s' is unavailable: No such backend" % database_type) else: return backend._verify() @@ -198,14 +271,15 @@ if not self.verified: try: eng = self._ensure_backend_available(self.url) - except exception.BackendNotAvailable: + except exception.BackendNotAvailable as bne: + self._no_engine_reason = str(bne) raise else: self.engine = eng finally: self.verified = True if self.engine is None: - raise exception.BackendNotAvailable(self.database_type) + raise exception.BackendNotAvailable(self._no_engine_reason) return self @classmethod @@ -220,7 +294,9 @@ LOG.info( _LI("The %(dbapi)s backend is unavailable: %(err)s"), dict(dbapi=url.drivername, err=i_e)) - raise exception.BackendNotAvailable("No DBAPI installed") + raise exception.BackendNotAvailable( + "Backend '%s' is unavailable: No DBAPI installed" % + url.drivername) else: try: conn = eng.connect() @@ -232,11 +308,17 @@ _LI("The %(dbapi)s backend is unavailable: %(err)s"), dict(dbapi=url.drivername, err=d_e) ) - raise exception.BackendNotAvailable("Could not connect") + raise exception.BackendNotAvailable( + "Backend '%s' is unavailable: Could not connect" % + url.drivername) else: conn.close() return eng + def _dispose(self): + """Dispose main resources of this backend.""" + self.impl.dispose(self.engine) + def create_named_database(self, ident, conditional=False): """Create a database with the given name.""" @@ -267,6 +349,20 @@ return self.impl.database_exists(self.engine, ident) + def provisioned_database_url(self, ident): + """Given the identifier of an anoymous database, return a URL. + + For hostname-based URLs, this typically involves switching just the + 'database' portion of the URL with the given name and creating + a URL. + + For SQLite URLs, the identifier may be used to create a filename + or may be ignored in the case of a memory database. + + """ + return self.impl.provisioned_database_url(self.url, ident) + + @debtcollector.removals.remove() def provisioned_engine(self, ident): """Given the URL of a particular database backend and the string @@ -274,14 +370,6 @@ an Engine instance whose connections will refer directly to the named database. - For hostname-based URLs, this typically involves switching just the - 'database' portion of the URL with the given name and creating - an engine. - - For URLs that instead deal with DSNs, the rules may be more custom; - for example, the engine may need to connect to the root URL and - then emit a command to switch to the named database. - """ return self.impl.provisioned_engine(self.url, ident) @@ -313,7 +401,8 @@ url = sa_url.make_url(url_str) m = re.match(r'([^+]+?)(?:\+(.+))?$', url.drivername) database_type = m.group(1) - Backend(database_type, url) + Backend.backends_by_database_type[database_type] = \ + Backend(database_type, url) @six.add_metaclass(abc.ABCMeta) @@ -331,6 +420,10 @@ supports_drop_fk = True + def dispose(self, engine): + LOG.info("DISPOSE ENGINE %s", engine) + engine.dispose() + @classmethod def all_impls(cls): """Return an iterator of all possible BackendImpl objects. @@ -364,7 +457,7 @@ def create_opportunistic_driver_url(self): """Produce a string url known as the 'opportunistic' URL. - This URL is one that corresponds to an established Openstack + This URL is one that corresponds to an established OpenStack convention for a pre-established database login, which, when detected as available in the local environment, is automatically used as a test platform for a specific type of driver. @@ -422,6 +515,28 @@ def drop_additional_objects(self, conn): pass + def provisioned_database_url(self, base_url, ident): + """Return a provisioned database URL. + + Given the URL of a particular database backend and the string + name of a particular 'database' within that backend, return + an URL which refers directly to the named database. + + For hostname-based URLs, this typically involves switching just the + 'database' portion of the URL with the given name and creating + an engine. + + For URLs that instead deal with DSNs, the rules may be more custom; + for example, the engine may need to connect to the root URL and + then emit a command to switch to the named database. + + """ + + url = sa_url.make_url(str(base_url)) + url.database = ident + return url + + @debtcollector.removals.remove() def provisioned_engine(self, base_url, ident): """Return a provisioned engine. @@ -439,9 +554,8 @@ then emit a command to switch to the named database. """ + url = self.provisioned_database_url(base_url, ident) - url = sa_url.make_url(str(base_url)) - url.database = ident return session.create_engine( url, logging_name="%s@%s" % (self.drivername, ident), @@ -452,6 +566,7 @@ @BackendImpl.impl.dispatch_for("mysql") class MySQLBackendImpl(BackendImpl): + # only used for deprecated provisioned_engine() function. default_engine_kwargs = {'mysql_sql_mode': 'TRADITIONAL'} def create_opportunistic_driver_url(self): @@ -476,22 +591,29 @@ supports_drop_fk = False + def dispose(self, engine): + LOG.info("DISPOSE ENGINE %s", engine) + engine.dispose() + url = engine.url + self._drop_url_file(url, True) + + def _drop_url_file(self, url, conditional): + filename = url.database + if filename and (not conditional or os.access(filename, os.F_OK)): + os.remove(filename) + def create_opportunistic_driver_url(self): return "sqlite://" def create_named_database(self, engine, ident, conditional=False): - url = self._provisioned_database_url(engine.url, ident) + url = self.provisioned_database_url(engine.url, ident) filename = url.database if filename and (not conditional or not os.access(filename, os.F_OK)): eng = sqlalchemy.create_engine(url) eng.connect().close() - def provisioned_engine(self, base_url, ident): - return session.create_engine( - self._provisioned_database_url(base_url, ident)) - def drop_named_database(self, engine, ident, conditional=False): - url = self._provisioned_database_url(engine.url, ident) + url = self.provisioned_database_url(engine.url, ident) filename = url.database if filename and (not conditional or os.access(filename, os.F_OK)): os.remove(filename) @@ -501,7 +623,7 @@ filename = url.database return not filename or os.access(filename, os.F_OK) - def _provisioned_database_url(self, base_url, ident): + def provisioned_database_url(self, base_url, ident): if base_url.database: return sa_url.make_url("sqlite:////tmp/%s.db" % ident) else: @@ -530,7 +652,7 @@ conn.execute("DROP DATABASE %s" % ident) def drop_additional_objects(self, conn): - enums = compat_utils.get_postgresql_enums(conn) + enums = [e['name'] for e in sqlalchemy.inspect(conn).get_enums()] for e in enums: conn.execute("DROP TYPE %s" % e) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_base.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_base.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_base.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_base.py 2017-01-18 14:09:32.000000000 +0000 @@ -13,9 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +import debtcollector +import debtcollector.moves import fixtures import testresources -import testscenarios try: from oslotest import base as test_base @@ -24,8 +25,6 @@ ' test-requirements') -import os - from oslo_utils import reflection import six @@ -33,8 +32,10 @@ from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import provision from oslo_db.sqlalchemy import session +from oslo_db.sqlalchemy.test_fixtures import optimize_package_test_loader +@debtcollector.removals.removed_class("DbFixture") class DbFixture(fixtures.Fixture): """Basic database fixture. @@ -65,9 +66,10 @@ testresources.tearDownResources, self.test, self.test.resources, testresources._get_result() ) - if not hasattr(self.test, 'db'): - msg = "backend '%s' unavailable" % self.DRIVER - if self.skip_on_unavailable_db: + + if not self.test._has_db_resource(): + msg = self.test._get_db_resource_not_available_reason() + if self.test.SKIP_ON_UNAVAILABLE_DB: self.test.skip(msg) else: self.test.fail(msg) @@ -89,6 +91,7 @@ self.addCleanup(self.test.enginefacade.dispose_global) +@debtcollector.removals.removed_class("DbTestCase") class DbTestCase(test_base.BaseTestCase): """Base class for testing of DB code. @@ -98,9 +101,17 @@ SCHEMA_SCOPE = None SKIP_ON_UNAVAILABLE_DB = True + _db_not_available = {} _schema_resources = {} _database_resources = {} + def _get_db_resource_not_available_reason(self): + return self._db_not_available.get(self.FIXTURE.DRIVER, None) + + def _has_db_resource(self): + return self._database_resources.get( + self.FIXTURE.DRIVER, None) is not None + def _resources_for_driver(self, driver, schema_scope, generate_schema): # testresources relies on the identity and state of the # TestResourceManager objects in play to correctly manage @@ -110,12 +121,15 @@ # so we have to code the TestResourceManager logic into the # .resources attribute and ensure that the same set of test # variables always produces the same TestResourceManager objects. + if driver not in self._database_resources: try: self._database_resources[driver] = \ - provision.DatabaseResource(driver) - except exception.BackendNotAvailable: + provision.DatabaseResource(driver, + provision_new_database=True) + except exception.BackendNotAvailable as bne: self._database_resources[driver] = None + self._db_not_available[driver] = str(bne) database_resource = self._database_resources[driver] if database_resource is None: @@ -180,6 +194,7 @@ "implemented within generate_schema().") +@debtcollector.removals.removed_class("OpportunisticTestCase") class OpportunisticTestCase(DbTestCase): """Placeholder for backwards compatibility.""" @@ -200,7 +215,7 @@ if self.engine.name not in dialects: msg = ('The test "%s" can be run ' 'only on %s. Current engine is %s.') - args = (reflection.get_callable_name(f), ' '.join(dialects), + args = (reflection.get_callable_name(f), ', '.join(dialects), self.engine.name) self.skip(msg % args) else: @@ -209,55 +224,25 @@ return wrap +@debtcollector.removals.removed_class("MySQLOpportunisticFixture") class MySQLOpportunisticFixture(DbFixture): DRIVER = 'mysql' +@debtcollector.removals.removed_class("PostgreSQLOpportunisticFixture") class PostgreSQLOpportunisticFixture(DbFixture): DRIVER = 'postgresql' +@debtcollector.removals.removed_class("MySQLOpportunisticTestCase") class MySQLOpportunisticTestCase(OpportunisticTestCase): FIXTURE = MySQLOpportunisticFixture +@debtcollector.removals.removed_class("PostgreSQLOpportunisticTestCase") class PostgreSQLOpportunisticTestCase(OpportunisticTestCase): FIXTURE = PostgreSQLOpportunisticFixture -def optimize_db_test_loader(file_): - """Package level load_tests() function. - - Will apply an optimizing test suite to all sub-tests, which groups DB - tests and other resources appropriately. - - Place this in an __init__.py package file within the root of the test - suite, at the level where testresources loads it as a package:: - - from oslo_db.sqlalchemy import test_base - - load_tests = test_base.optimize_db_test_loader(__file__) - - Alternatively, the directive can be placed into a test module directly. - - """ - - this_dir = os.path.dirname(file_) - - def load_tests(loader, found_tests, pattern): - # pattern is None if the directive is placed within - # a test module directly, as well as within certain test - # discovery patterns - - if pattern is not None: - pkg_tests = loader.discover(start_dir=this_dir, pattern=pattern) - - result = testresources.OptimisingTestSuite() - found_tests = testscenarios.load_tests_apply_scenarios( - loader, found_tests, pattern) - result.addTest(found_tests) - - if pattern is not None: - result.addTest(pkg_tests) - return result - return load_tests +optimize_db_test_loader = debtcollector.moves.moved_function( + optimize_package_test_loader, "optimize_db_test_loader", __name__) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_fixtures.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_fixtures.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_fixtures.py 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_fixtures.py 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,626 @@ +# Copyright (c) 2016 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures +import logging +import os +import testresources +import testscenarios + +from oslo_db import exception +from oslo_db.sqlalchemy import enginefacade +from oslo_db.sqlalchemy import provision +from oslo_db.sqlalchemy import utils + + +LOG = logging.getLogger(__name__) + + +class ReplaceEngineFacadeFixture(fixtures.Fixture): + """A fixture that will plug the engine of one enginefacade into another. + + This fixture can be used by test suites that already have their own non- + oslo_db database setup / teardown schemes, to plug any URL or test-oriented + enginefacade as-is into an enginefacade-oriented API. + + For applications that use oslo.db's testing fixtures, the + ReplaceEngineFacade fixture is used internally. + + E.g.:: + + class MyDBTest(TestCase): + + def setUp(self): + from myapplication.api import main_enginefacade + + my_test_enginefacade = enginefacade.transaction_context() + my_test_enginefacade.configure(connection=my_test_url) + + self.useFixture( + ReplaceEngineFacadeFixture( + main_enginefacade, my_test_enginefacade)) + + Above, the main_enginefacade object is the normal application level + one, and my_test_enginefacade is a local one that we've created to + refer to some testing database. Throughout the fixture's setup, + the application level enginefacade will use the engine factory and + engines of the testing enginefacade, and at fixture teardown will be + replaced back. + + """ + def __init__(self, enginefacade, replace_with_enginefacade): + super(ReplaceEngineFacadeFixture, self).__init__() + self.enginefacade = enginefacade + self.replace_with_enginefacade = replace_with_enginefacade + + def _setUp(self): + _reset_facade = self.enginefacade.patch_factory( + self.replace_with_enginefacade._factory + ) + self.addCleanup(_reset_facade) + + +class BaseDbFixture(fixtures.Fixture): + """Base database provisioning fixture. + + This serves as the base class for the other fixtures, but by itself + does not implement _setUp(). It provides the basis for the flags + implemented by the various capability mixins (GenerateSchema, + DeletesFromSchema, etc.) as well as providing an abstraction over + the provisioning objects, which are specific to testresources. + Overall, consumers of this fixture just need to use the right classes + and the testresources mechanics are taken care of. + + """ + DRIVER = "sqlite" + + _DROP_SCHEMA_PER_TEST = True + _BUILD_SCHEMA = False + _BUILD_WITH_MIGRATIONS = False + + _database_resources = {} + _db_not_available = {} + _schema_resources = {} + + def __init__(self, driver=None, ident=None): + super(BaseDbFixture, self).__init__() + self.driver = driver or self.DRIVER + self.ident = ident or "default" + self.resource_key = (self.driver, self.__class__, self.ident) + + def get_enginefacade(self): + """Return an enginefacade._TransactionContextManager. + + This is typically a global variable like "context_manager" declared + in the db/api.py module and is the object returned by + enginefacade.transaction_context(). + + If left not implemented, the global enginefacade manager is used. + + For the case where a project uses per-object or per-test enginefacades + like Gnocchi, the get_per_test_enginefacade() + method should also be implemented. + + + """ + return enginefacade._context_manager + + def get_per_test_enginefacade(self): + """Return an enginefacade._TransactionContextManager per test. + + This facade should be the one that the test expects the code to + use. Usually this is the same one returned by get_engineafacade() + which is the default. For special applications like Gnocchi, + this can be overridden to provide an instance-level facade. + + """ + return self.get_enginefacade() + + def _get_db_resource_not_available_reason(self): + return self._db_not_available.get(self.resource_key, None) + + def _has_db_resource(self): + return self._database_resources.get( + self.resource_key, None) is not None + + def _generate_schema_resource(self, database_resource): + return provision.SchemaResource( + database_resource, + None if not self._BUILD_SCHEMA + else self.generate_schema_create_all + if not self._BUILD_WITH_MIGRATIONS + else self.generate_schema_migrations, + self._DROP_SCHEMA_PER_TEST + ) + + def _get_resources(self): + key = self.resource_key + + # the DatabaseResource and SchemaResource provision objects + # can be used by testresources as a marker outside of an individual + # test to indicate that this database / schema can be used across + # multiple tests. To make this work, many instances of this + # fixture have to return the *same* resource object given the same + # inputs. so we cache these in class-level dictionaries. + + if key not in self._database_resources: + _enginefacade = self.get_enginefacade() + try: + self._database_resources[key] = \ + self._generate_database_resource(_enginefacade) + except exception.BackendNotAvailable as bne: + self._database_resources[key] = None + self._db_not_available[key] = str(bne) + + database_resource = self._database_resources[key] + + if database_resource is None: + return [] + else: + if key in self._schema_resources: + schema_resource = self._schema_resources[key] + else: + schema_resource = self._schema_resources[key] = \ + self._generate_schema_resource(database_resource) + + return [ + ('_schema_%s' % self.ident, schema_resource), + ('_db_%s' % self.ident, database_resource) + ] + + +class GeneratesSchema(object): + """Mixin defining a fixture as generating a schema using create_all(). + + This is a "capability" mixin that works in conjunction with classes + that include BaseDbFixture as a base. + + """ + + _BUILD_SCHEMA = True + _BUILD_WITH_MIGRATIONS = False + + def generate_schema_create_all(self, engine): + """A hook which should generate the model schema using create_all(). + + This hook is called within the scope of creating the database + assuming BUILD_WITH_MIGRATIONS is False. + + """ + + +class GeneratesSchemaFromMigrations(GeneratesSchema): + """Mixin defining a fixture as generating a schema using migrations. + + This is a "capability" mixin that works in conjunction with classes + that include BaseDbFixture as a base. + + """ + + _BUILD_WITH_MIGRATIONS = True + + def generate_schema_migrations(self, engine): + """A hook which should generate the model schema using migrations. + + + This hook is called within the scope of creating the database + assuming BUILD_WITH_MIGRATIONS is True. + + """ + + +class ResetsData(object): + """Mixin defining a fixture that resets schema data without dropping.""" + + _DROP_SCHEMA_PER_TEST = False + + def setup_for_reset(self, engine, enginefacade): + """"Perform setup that may be needed before the test runs.""" + + def reset_schema_data(self, engine, enginefacade): + """Reset the data in the schema.""" + + +class DeletesFromSchema(ResetsData): + """Mixin defining a fixture that can delete from all tables in place. + + When DeletesFromSchema is present in a fixture, + _DROP_SCHEMA_PER_TEST is now False; this means that the + "teardown" flag of provision.SchemaResource will be False, which + prevents SchemaResource from dropping all objects within the schema + after each test. + + This is a "capability" mixin that works in conjunction with classes + that include BaseDbFixture as a base. + + """ + + def reset_schema_data(self, engine, facade): + self.delete_from_schema(engine) + + def delete_from_schema(self, engine): + """A hook which should delete all data from an existing schema. + + Should *not* drop any objects, just remove data from tables + that needs to be reset between tests. + """ + + +class RollsBackTransaction(ResetsData): + """Fixture class that maintains a database transaction per test. + + """ + + def setup_for_reset(self, engine, facade): + conn = engine.connect() + engine = utils.NonCommittingEngine(conn) + self._reset_engine = enginefacade._TestTransactionFactory.apply_engine( + engine, facade) + + def reset_schema_data(self, engine, facade): + self._reset_engine() + engine._dispose() + + +class SimpleDbFixture(BaseDbFixture): + """Fixture which provides an engine from a fixed URL. + + The SimpleDbFixture is generally appropriate only for a SQLite memory + database, as this database is naturally isolated from other processes and + does not require management of schemas. For tests that need to + run specifically against MySQL or Postgresql, the OpportunisticDbFixture + is more appropriate. + + The database connection information itself comes from the provisoning + system, matching the desired driver (typically sqlite) to the default URL + that provisioning provides for this driver (in the case of sqlite, it's + the SQLite memory URL, e.g. sqlite://. For MySQL and Postgresql, it's + the familiar "openstack_citest" URL on localhost). + + There are a variety of create/drop schemes that can take place: + + * The default is to procure a database connection on setup, + and at teardown, an instruction is issued to "drop" all + objects in the schema (e.g. tables, indexes). The SQLAlchemy + engine itself remains referenced at the class level for subsequent + re-use. + + * When the GeneratesSchema or GeneratesSchemaFromMigrations mixins + are implemented, the appropriate generate_schema method is also + called when the fixture is set up, by default this is per test. + + * When the DeletesFromSchema mixin is implemented, the generate_schema + method is now only called **once**, and the "drop all objects" + system is replaced with the delete_from_schema method. This + allows the same database to remain set up with all schema objects + intact, so that expensive migrations need not be run on every test. + + * The fixture does **not** dispose the engine at the end of a test. + It is assumed the same engine will be re-used many times across + many tests. The AdHocDbFixture extends this one to provide + engine.dispose() at the end of a test. + + This fixture is intended to work without needing a reference to + the test itself, and therefore cannot take advantage of the + OptimisingTestSuite. + + """ + + _dependency_resources = {} + + def _get_provisioned_db(self): + return self._dependency_resources["_db_%s" % self.ident] + + def _generate_database_resource(self, _enginefacade): + return provision.DatabaseResource(self.driver, _enginefacade, + provision_new_database=False) + + def _setUp(self): + super(SimpleDbFixture, self)._setUp() + + cls = self.__class__ + + if "_db_%s" % self.ident not in cls._dependency_resources: + + resources = self._get_resources() + + # initialize resources the same way that testresources does. + for name, resource in resources: + cls._dependency_resources[name] = resource.getResource() + + provisioned_db = self._get_provisioned_db() + + if not self._DROP_SCHEMA_PER_TEST: + self.setup_for_reset( + provisioned_db.engine, provisioned_db.enginefacade) + + self.useFixture(ReplaceEngineFacadeFixture( + self.get_per_test_enginefacade(), + provisioned_db.enginefacade + )) + + if not self._DROP_SCHEMA_PER_TEST: + self.addCleanup( + self.reset_schema_data, + provisioned_db.engine, provisioned_db.enginefacade) + + self.addCleanup(self._cleanup) + + def _teardown_resources(self): + for name, resource in self._get_resources(): + dep = self._dependency_resources.pop(name) + resource.finishedWith(dep) + + def _cleanup(self): + pass + + +class AdHocDbFixture(SimpleDbFixture): + """"Fixture which creates and disposes a database engine per test. + + Also allows a specific URL to be passed, meaning the fixture can + be hardcoded to a specific SQLite file. + + For a SQLite, this fixture will create the named database upon setup + and tear it down upon teardown. For other databases, the + database is assumed to exist already and will remain after teardown. + + """ + def __init__(self, url=None): + if url: + self.url = provision.sa_url.make_url(str(url)) + driver = self.url.get_backend_name() + else: + driver = None + self.url = None + + BaseDbFixture.__init__( + self, driver=driver, + ident=provision._random_ident()) + self.url = url + + def _generate_database_resource(self, _enginefacade): + return provision.DatabaseResource( + self.driver, _enginefacade, ad_hoc_url=self.url, + provision_new_database=False) + + def _cleanup(self): + self._teardown_resources() + + +class OpportunisticDbFixture(BaseDbFixture): + """Fixture which uses testresources fully for optimised runs. + + This fixture relies upon the use of the OpportunisticDBTestMixin to supply + a test.resources attribute, and also works much more effectively when + combined the testresources.OptimisingTestSuite. The + optimize_db_test_loader() function should be used at the module and package + levels to optimize database provisioning across many tests. + + """ + def __init__(self, test, driver=None, ident=None): + super(OpportunisticDbFixture, self).__init__( + driver=driver, ident=ident) + self.test = test + + def _get_provisioned_db(self): + return getattr(self.test, "_db_%s" % self.ident) + + def _generate_database_resource(self, _enginefacade): + return provision.DatabaseResource( + self.driver, _enginefacade, provision_new_database=True) + + def _setUp(self): + super(OpportunisticDbFixture, self)._setUp() + + if not self._has_db_resource(): + return + + provisioned_db = self._get_provisioned_db() + + if not self._DROP_SCHEMA_PER_TEST: + self.setup_for_reset( + provisioned_db.engine, provisioned_db.enginefacade) + + self.useFixture(ReplaceEngineFacadeFixture( + self.get_per_test_enginefacade(), + provisioned_db.enginefacade + )) + + if not self._DROP_SCHEMA_PER_TEST: + self.addCleanup( + self.reset_schema_data, + provisioned_db.engine, provisioned_db.enginefacade) + + +class OpportunisticDBTestMixin(object): + """Test mixin that integrates the test suite with testresources. + + There are three goals to this system: + + 1. Allow creation of "stub" test suites that will run all the tests in a + parent suite against a specific kind of database (e.g. Mysql, + Postgresql), where the entire suite will be skipped if that target + kind of database is not available to the suite. + + 2. provide a test with a process-local, anonymously named schema within a + target database, so that the test can run concurrently with other tests + without conflicting data + + 3. provide compatibility with the testresources.OptimisingTestSuite, which + organizes TestCase instances ahead of time into groups that all + make use of the same type of database, setting up and tearing down + a database schema once for the scope of any number of tests within. + This technique is essential when testing against a non-SQLite database + because building of a schema is expensive, and also is most ideally + accomplished using the applications schema migration which are + even more vastly slow than a straight create_all(). + + This mixin provides the .resources attribute required by testresources when + using the OptimisingTestSuite.The .resources attribute then provides a + collection of testresources.TestResourceManager objects, which are defined + here in oslo_db.sqlalchemy.provision. These objects know how to find + available database backends, build up temporary databases, and invoke + schema generation and teardown instructions. The actual "build the schema + objects" part of the equation, and optionally a "delete from all the + tables" step, is provided by the implementing application itself. + + + """ + SKIP_ON_UNAVAILABLE_DB = True + + FIXTURE = OpportunisticDbFixture + + _collected_resources = None + _instantiated_fixtures = None + + @property + def resources(self): + """Provide a collection of TestResourceManager objects. + + The collection here is memoized, both at the level of the test + case itself, as well as in the fixture object(s) which provide + those resources. + + """ + + if self._collected_resources is not None: + return self._collected_resources + + fixtures = self._instantiate_fixtures() + self._collected_resources = [] + for fixture in fixtures: + self._collected_resources.extend(fixture._get_resources()) + return self._collected_resources + + def setUp(self): + self._setup_fixtures() + super(OpportunisticDBTestMixin, self).setUp() + + def _get_default_provisioned_db(self): + return self._db_default + + def _instantiate_fixtures(self): + if self._instantiated_fixtures: + return self._instantiated_fixtures + + self._instantiated_fixtures = utils.to_list(self.generate_fixtures()) + return self._instantiated_fixtures + + def generate_fixtures(self): + return self.FIXTURE(test=self) + + def _setup_fixtures(self): + testresources.setUpResources( + self, self.resources, testresources._get_result()) + self.addCleanup( + testresources.tearDownResources, + self, self.resources, testresources._get_result() + ) + + fixtures = self._instantiate_fixtures() + for fixture in fixtures: + self.useFixture(fixture) + + if not fixture._has_db_resource(): + msg = fixture._get_db_resource_not_available_reason() + if self.SKIP_ON_UNAVAILABLE_DB: + self.skip(msg) + else: + self.fail(msg) + + +class MySQLOpportunisticFixture(OpportunisticDbFixture): + DRIVER = 'mysql' + + +class PostgresqlOpportunisticFixture(OpportunisticDbFixture): + DRIVER = 'postgresql' + + +def optimize_package_test_loader(file_): + """Organize package-level tests into a testresources.OptimizingTestSuite. + + This function provides a unittest-compatible load_tests hook + for a given package; for per-module, use the + :func:`.optimize_module_test_loader` function. + + When a unitest or subunit style + test runner is used, the function will be called in order to + return a TestSuite containing the tests to run; this function + ensures that this suite is an OptimisingTestSuite, which will organize + the production of test resources across groups of tests at once. + + The function is invoked as:: + + from oslo_db.sqlalchemy import test_base + + load_tests = test_base.optimize_package_test_loader(__file__) + + The loader *must* be present in the package level __init__.py. + + The function also applies testscenarios expansion to all test collections. + This so that an existing test suite that already needs to build + TestScenarios from a load_tests call can still have this take place when + replaced with this function. + + """ + + this_dir = os.path.dirname(file_) + + def load_tests(loader, found_tests, pattern): + result = testresources.OptimisingTestSuite() + result.addTests(found_tests) + pkg_tests = loader.discover(start_dir=this_dir, pattern=pattern) + result.addTests(testscenarios.generate_scenarios(pkg_tests)) + + return result + return load_tests + + +def optimize_module_test_loader(): + """Organize module-level tests into a testresources.OptimizingTestSuite. + + This function provides a unittest-compatible load_tests hook + for a given module; for per-package, use the + :func:`.optimize_package_test_loader` function. + + When a unitest or subunit style + test runner is used, the function will be called in order to + return a TestSuite containing the tests to run; this function + ensures that this suite is an OptimisingTestSuite, which will organize + the production of test resources across groups of tests at once. + + The function is invoked as:: + + from oslo_db.sqlalchemy import test_base + + load_tests = test_base.optimize_module_test_loader() + + The loader *must* be present in an individual module, and *not* the + package level __init__.py. + + The function also applies testscenarios expansion to all test collections. + This so that an existing test suite that already needs to build + TestScenarios from a load_tests call can still have this take place when + replaced with this function. + + """ + + def load_tests(loader, found_tests, pattern): + result = testresources.OptimisingTestSuite() + result.addTests(testscenarios.generate_scenarios(found_tests)) + return result + return load_tests diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_migrations.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_migrations.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/test_migrations.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/test_migrations.py 2017-01-18 14:09:32.000000000 +0000 @@ -32,6 +32,7 @@ from oslo_db._i18n import _LE from oslo_db import exception as exc +from oslo_db.sqlalchemy import provision from oslo_db.sqlalchemy import utils LOG = logging.getLogger(__name__) @@ -268,7 +269,7 @@ check = getattr(self, "_check_%03d" % version, None) if check: check(self.migrate_engine, data) - except exc.DbMigrationError: + except exc.DBMigrationError: msg = _LE("Failed to migrate to version %(ver)s on engine %(eng)s") LOG.error(msg, {"ver": version, "eng": self.migrate_engine}) raise @@ -481,7 +482,10 @@ isinstance(meta_def.arg, expr.False_) and insp_def == "'0'" ) - if isinstance(meta_col.type, sqlalchemy.Integer): + impl_type = meta_col.type + if isinstance(impl_type, types.Variant): + impl_type = impl_type.load_dialect_impl(bind.dialect) + if isinstance(impl_type, (sqlalchemy.Integer, sqlalchemy.BigInteger)): if meta_def is None or insp_def is None: return meta_def != insp_def return meta_def.arg != insp_def.split("'")[1] @@ -591,9 +595,10 @@ self.skipTest('sqlalchemy>=0.8.4 and alembic>=0.6.3 are required' ' for running of this test: %s' % e) - # drop all tables after a test run - self.addCleanup(functools.partial(self.db.backend.drop_all_objects, - self.get_engine())) + # drop all objects after a test run + engine = self.get_engine() + backend = provision.Backend(engine.name, engine.url) + self.addCleanup(functools.partial(backend.drop_all_objects, engine)) # run migration scripts self.db_sync(self.get_engine()) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/types.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/types.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/types.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/types.py 2017-01-18 14:09:32.000000000 +0000 @@ -12,7 +12,8 @@ import json -from sqlalchemy.types import TypeDecorator, Text +from sqlalchemy.types import Integer, TypeDecorator, Text +from sqlalchemy.dialects import mysql class JsonEncodedType(TypeDecorator): @@ -20,6 +21,18 @@ type = None impl = Text + def __init__(self, mysql_as_long=False, mysql_as_medium=False): + super(JsonEncodedType, self).__init__() + + if mysql_as_long and mysql_as_medium: + raise TypeError("mysql_as_long and mysql_as_medium are mutually " + "exclusive") + + if mysql_as_long: + self.impl = Text().with_variant(mysql.LONGTEXT(), 'mysql') + elif mysql_as_medium: + self.impl = Text().with_variant(mysql.MEDIUMTEXT(), 'mysql') + def process_bind_param(self, value, dialect): if value is None: if self.type is not None: @@ -60,3 +73,31 @@ http://docs.sqlalchemy.org/en/rel_1_0/orm/extensions/mutable.html """ type = list + + +class SoftDeleteInteger(TypeDecorator): + """Coerce a bound param to be a proper integer before passing it to DBAPI. + + Some backends like PostgreSQL are very strict about types and do not + perform automatic type casts, e.g. when trying to INSERT a boolean value + like ``false`` into an integer column. Coercing of the bound param in DB + layer by the means of a custom SQLAlchemy type decorator makes sure we + always pass a proper integer value to a DBAPI implementation. + + This is not a general purpose boolean integer type as it specifically + allows for arbitrary positive integers outside of the boolean int range + (0, 1, False, True), so that it's possible to have compound unique + constraints over multiple columns including ``deleted`` (e.g. to + soft-delete flavors with the same name in Nova without triggering + a constraint violation): ``deleted`` is set to be equal to a PK + int value on deletion, 0 denotes a non-deleted row. + + """ + + impl = Integer + + def process_bind_param(self, value, dialect): + if value is None: + return None + else: + return int(value) diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/update_match.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/update_match.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/update_match.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/update_match.py 2017-01-18 14:09:32.000000000 +0000 @@ -72,7 +72,7 @@ # try again return False - peristent_instance = base_query.update_on_match( + persistent_instance = base_query.update_on_match( specimen, surrogate_key, values=values, diff -Nru python-oslo.db-4.7.0/oslo_db/sqlalchemy/utils.py python-oslo.db-4.17.0/oslo_db/sqlalchemy/utils.py --- python-oslo.db-4.7.0/oslo_db/sqlalchemy/utils.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/sqlalchemy/utils.py 2017-01-18 14:09:32.000000000 +0000 @@ -22,6 +22,7 @@ import logging import re +import debtcollector from oslo_utils import timeutils import six import sqlalchemy @@ -31,6 +32,7 @@ from sqlalchemy.engine import Connectable from sqlalchemy.engine import reflection from sqlalchemy.engine import url as sa_url +from sqlalchemy import exc from sqlalchemy import func from sqlalchemy import Index from sqlalchemy import inspect @@ -66,6 +68,71 @@ return url +def get_unique_keys(model): + """Get a list of sets of unique model keys. + + :param model: the ORM model class + :rtype: list of sets of strings + :return: unique model keys or None if unable to find them + """ + + try: + mapper = inspect(model) + except exc.NoInspectionAvailable: + return None + else: + local_table = mapper.local_table + base_table = mapper.base_mapper.local_table + + if local_table is None: + return None + + # extract result from cache if present + has_info = hasattr(local_table, 'info') + if has_info: + info = local_table.info + if 'oslodb_unique_keys' in info: + return info['oslodb_unique_keys'] + + res = [] + try: + constraints = base_table.constraints + except AttributeError: + constraints = [] + for constraint in constraints: + # filter out any CheckConstraints + if isinstance(constraint, (sqlalchemy.UniqueConstraint, + sqlalchemy.PrimaryKeyConstraint)): + res.append({c.name for c in constraint.columns}) + try: + indexes = base_table.indexes + except AttributeError: + indexes = [] + for index in indexes: + if index.unique: + res.append({c.name for c in index.columns}) + # cache result for next calls with the same model + if has_info: + info['oslodb_unique_keys'] = res + return res + + +def _stable_sorting_order(model, sort_keys): + """Check whether the sorting order is stable. + + :return: True if it is stable, False if it's not, None if it's impossible + to determine. + """ + keys = get_unique_keys(model) + if keys is None: + return None + sort_keys_set = set(sort_keys) + for unique_keys in keys: + if unique_keys.issubset(sort_keys_set): + return True + return False + + # copy from glance/db/sqlalchemy/api.py def paginate_query(query, model, limit, sort_keys, marker=None, sort_dir=None, sort_dirs=None): @@ -80,7 +147,8 @@ the lexicographical ordering: (k1 > X1) or (k1 == X1 && k2 > X2) or (k1 == X1 && k2 == X2 && k3 > X3) - We also have to cope with different sort_directions. + We also have to cope with different sort_directions and cases where k2, + k3, ... are nullable. Typically, the id of the last row is used as the client-facing pagination marker, then the actual marker object must be fetched from the db and @@ -100,11 +168,9 @@ :rtype: sqlalchemy.orm.query.Query :return: The query with sorting/pagination added. """ - - if 'id' not in sort_keys: - # TODO(justinsb): If this ever gives a false-positive, check - # the actual primary key, rather than assuming its id - LOG.warning(_LW('Id not in sort_keys; is sort_keys unique?')) + if _stable_sorting_order(model, sort_keys) is False: + LOG.warning(_LW('Unique keys not in sort_keys. ' + 'The sorting order may be unstable.')) assert(not (sort_dir and sort_dirs)) @@ -159,18 +225,24 @@ criteria_list = [] for i in range(len(sort_keys)): crit_attrs = [] - for j in range(i): - model_attr = getattr(model, sort_keys[j]) - crit_attrs.append((model_attr == marker_values[j])) - - model_attr = getattr(model, sort_keys[i]) - if sort_dirs[i].startswith('desc'): - crit_attrs.append((model_attr < marker_values[i])) - else: - crit_attrs.append((model_attr > marker_values[i])) + # NOTE: We skip the marker value comparison if marker_values[i] is + # None, for two reasons: 1) the comparison operators below + # ('<', '>') are not applicable on None value; 2) this is + # safe because we can assume the primary key is included in + # sort_key, thus checked as (one of) marker values. + if marker_values[i] is not None: + for j in range(i): + model_attr = getattr(model, sort_keys[j]) + crit_attrs.append((model_attr == marker_values[j])) + + model_attr = getattr(model, sort_keys[i]) + if sort_dirs[i].startswith('desc'): + crit_attrs.append((model_attr < marker_values[i])) + else: + crit_attrs.append((model_attr > marker_values[i])) - criteria = sqlalchemy.sql.and_(*crit_attrs) - criteria_list.append(criteria) + criteria = sqlalchemy.sql.and_(*crit_attrs) + criteria_list.append(criteria) f = sqlalchemy.sql.or_(*criteria_list) query = query.filter(f) @@ -351,13 +423,14 @@ return Table(name, metadata, autoload=True) +@debtcollector.removals.removed_class( + 'InsertFromSelect', + replacement='sqlalchemy.sql.expression.Insert.from_select', + message='this functionality is provided out-of-box by SQLAlchemy >= 1.0.0' +) class InsertFromSelect(object): """Form the base for `INSERT INTO table (SELECT ... )` statement. - DEPRECATED: this class is deprecated and will be removed from oslo_db - in a few releases. Use default SQLAlchemy insert from select implementation - instead - :param table: table to insert records :param select: select query :param cols: list of columns to specify in insert clause @@ -404,21 +477,21 @@ return column -def drop_old_duplicate_entries_from_table(migrate_engine, table_name, +def drop_old_duplicate_entries_from_table(engine, table_name, use_soft_delete, *uc_column_names): """Drop all old rows having the same values for columns in uc_columns. This method drop (or mark ad `deleted` if use_soft_delete is True) old duplicate rows form table with name `table_name`. - :param migrate_engine: Sqlalchemy engine + :param engine: Sqlalchemy engine :param table_name: Table with duplicates :param use_soft_delete: If True - values will be marked as `deleted`, if False - values will be removed from table :param uc_column_names: Unique constraint columns """ meta = MetaData() - meta.bind = migrate_engine + meta.bind = engine table = Table(table_name, meta, autoload=True) columns_for_group_by = [table.c[name] for name in uc_column_names] @@ -430,7 +503,7 @@ columns_for_select, group_by=columns_for_group_by, having=func.count(table.c.id) > 1) - for row in migrate_engine.execute(duplicated_rows_select).fetchall(): + for row in engine.execute(duplicated_rows_select).fetchall(): # NOTE(boris-42): Do not remove row that has the biggest ID. delete_condition = table.c.id != row[0] is_none = None # workaround for pyflakes @@ -440,7 +513,7 @@ rows_to_delete_select = sqlalchemy.sql.select( [table.c.id]).where(delete_condition) - for row in migrate_engine.execute(rows_to_delete_select).fetchall(): + for row in engine.execute(rows_to_delete_select).fetchall(): LOG.info(_LI("Deleting duplicated row with id: %(id)s from table: " "%(table)s"), dict(id=row[0], table=table_name)) @@ -454,7 +527,7 @@ }) else: delete_statement = table.delete().where(delete_condition) - migrate_engine.execute(delete_statement) + engine.execute(delete_statement) def _get_default_deleted_value(table): @@ -465,11 +538,10 @@ raise exception.ColumnError(_("Unsupported id columns type")) -def _restore_indexes_on_deleted_columns(migrate_engine, table_name, indexes): - table = get_table(migrate_engine, table_name) +def _restore_indexes_on_deleted_columns(engine, table_name, indexes): + table = get_table(engine, table_name) - insp = reflection.Inspector.from_engine(migrate_engine) - real_indexes = insp.get_indexes(table_name) + real_indexes = get_indexes(engine, table_name) existing_index_names = dict( [(index['name'], index['column_names']) for index in real_indexes]) @@ -481,22 +553,20 @@ if name in existing_index_names: column_names = [table.c[c] for c in existing_index_names[name]] old_index = Index(name, *column_names, unique=index["unique"]) - old_index.drop(migrate_engine) + old_index.drop(engine) column_names = [table.c[c] for c in index['column_names']] new_index = Index(index["name"], *column_names, unique=index["unique"]) - new_index.create(migrate_engine) + new_index.create(engine) -def change_deleted_column_type_to_boolean(migrate_engine, table_name, +def change_deleted_column_type_to_boolean(engine, table_name, **col_name_col_instance): - if migrate_engine.name == "sqlite": + if engine.name == "sqlite": return _change_deleted_column_type_to_boolean_sqlite( - migrate_engine, table_name, **col_name_col_instance) - insp = reflection.Inspector.from_engine(migrate_engine) - indexes = insp.get_indexes(table_name) - - table = get_table(migrate_engine, table_name) + engine, table_name, **col_name_col_instance) + indexes = get_indexes(engine, table_name) + table = get_table(engine, table_name) old_deleted = Column('old_deleted', Boolean, default=False) old_deleted.create(table, populate_default=False) @@ -509,14 +579,12 @@ table.c.deleted.drop() table.c.old_deleted.alter(name="deleted") - _restore_indexes_on_deleted_columns(migrate_engine, table_name, indexes) + _restore_indexes_on_deleted_columns(engine, table_name, indexes) -def _change_deleted_column_type_to_boolean_sqlite(migrate_engine, table_name, +def _change_deleted_column_type_to_boolean_sqlite(engine, table_name, **col_name_col_instance): - insp = reflection.Inspector.from_engine(migrate_engine) - table = get_table(migrate_engine, table_name) - + table = get_table(engine, table_name) columns = [] for column in table.columns: column_copy = None @@ -538,7 +606,7 @@ new_table.create() indexes = [] - for index in insp.get_indexes(table_name): + for index in get_indexes(engine, table_name): column_names = [new_table.c[c] for c in index['column_names']] indexes.append(Index(index["name"], *column_names, unique=index["unique"])) @@ -551,11 +619,11 @@ c_select.append(table.c.deleted == table.c.id) ins = InsertFromSelect(new_table, sqlalchemy.sql.select(c_select)) - migrate_engine.execute(ins) + engine.execute(ins) table.drop() for index in indexes: - index.create(migrate_engine) + index.create(engine) new_table.rename(table_name) new_table.update().\ @@ -564,15 +632,13 @@ execute() -def change_deleted_column_type_to_id_type(migrate_engine, table_name, +def change_deleted_column_type_to_id_type(engine, table_name, **col_name_col_instance): - if migrate_engine.name == "sqlite": + if engine.name == "sqlite": return _change_deleted_column_type_to_id_type_sqlite( - migrate_engine, table_name, **col_name_col_instance) - insp = reflection.Inspector.from_engine(migrate_engine) - indexes = insp.get_indexes(table_name) - - table = get_table(migrate_engine, table_name) + engine, table_name, **col_name_col_instance) + indexes = get_indexes(engine, table_name) + table = get_table(engine, table_name) new_deleted = Column('new_deleted', table.c.id.type, default=_get_default_deleted_value(table)) @@ -586,10 +652,23 @@ table.c.deleted.drop() table.c.new_deleted.alter(name="deleted") - _restore_indexes_on_deleted_columns(migrate_engine, table_name, indexes) + _restore_indexes_on_deleted_columns(engine, table_name, indexes) + + +def _is_deleted_column_constraint(constraint): + # NOTE(boris-42): There is no other way to check is CheckConstraint + # associated with deleted column. + if not isinstance(constraint, CheckConstraint): + return False + sqltext = str(constraint.sqltext) + # NOTE(zzzeek): SQLite never reflected CHECK contraints here + # in any case until version 1.1. Safe to assume that any CHECK + # that's talking about the value of "deleted in (something)" is + # the boolean constraint we're looking to get rid of. + return bool(re.match(r".*deleted in \(.*\)", sqltext, re.I)) -def _change_deleted_column_type_to_id_type_sqlite(migrate_engine, table_name, +def _change_deleted_column_type_to_id_type_sqlite(engine, table_name, **col_name_col_instance): # NOTE(boris-42): sqlaclhemy-migrate can't drop column with check # constraints in sqlite DB and our `deleted` column has @@ -600,8 +679,7 @@ # 2) Copy all data from old to new table. # 3) Drop old table. # 4) Rename new table to old table name. - insp = reflection.Inspector.from_engine(migrate_engine) - meta = MetaData(bind=migrate_engine) + meta = MetaData(bind=engine) table = Table(table_name, meta, autoload=True) default_deleted_value = _get_default_deleted_value(table) @@ -619,25 +697,9 @@ default=default_deleted_value) columns.append(column_copy) - def is_deleted_column_constraint(constraint): - # NOTE(boris-42): There is no other way to check is CheckConstraint - # associated with deleted column. - if not isinstance(constraint, CheckConstraint): - return False - sqltext = str(constraint.sqltext) - # NOTE(I159): in order to omit the CHECK constraint corresponding - # to `deleted` column we have to test these patterns which may - # vary depending on the SQLAlchemy version used. - constraint_markers = ( - "deleted in (0, 1)", - "deleted IN (:deleted_1, :deleted_2)", - "deleted IN (:param_1, :param_2)" - ) - return any(sqltext.endswith(marker) for marker in constraint_markers) - constraints = [] for constraint in table.constraints: - if not is_deleted_column_constraint(constraint): + if not _is_deleted_column_constraint(constraint): constraints.append(constraint.copy()) new_table = Table(table_name + "__tmp__", meta, @@ -645,17 +707,17 @@ new_table.create() indexes = [] - for index in insp.get_indexes(table_name): + for index in get_indexes(engine, table_name): column_names = [new_table.c[c] for c in index['column_names']] indexes.append(Index(index["name"], *column_names, unique=index["unique"])) ins = InsertFromSelect(new_table, table.select()) - migrate_engine.execute(ins) + engine.execute(ins) table.drop() for index in indexes: - index.create(migrate_engine) + index.create(engine) new_table.rename(table_name) deleted = True # workaround for pyflakes @@ -734,29 +796,55 @@ return (user, password, database, host) -def index_exists(migrate_engine, table_name, index_name): - """Check if given index exists. +def get_indexes(engine, table_name): + """Get all index list from a given table. - :param migrate_engine: sqlalchemy engine - :param table_name: name of the table - :param index_name: name of the index + :param engine: sqlalchemy engine + :param table_name: name of the table """ - inspector = reflection.Inspector.from_engine(migrate_engine) + + inspector = reflection.Inspector.from_engine(engine) indexes = inspector.get_indexes(table_name) + return indexes + + +def index_exists(engine, table_name, index_name): + """Check if given index exists. + + :param engine: sqlalchemy engine + :param table_name: name of the table + :param index_name: name of the index + """ + indexes = get_indexes(engine, table_name) index_names = [index['name'] for index in indexes] return index_name in index_names -def add_index(migrate_engine, table_name, index_name, idx_columns): +def index_exists_on_columns(engine, table_name, columns): + """Check if an index on given columns exists. + + :param engine: sqlalchemy engine + :param table_name: name of the table + :param columns: a list type of columns that will be checked + """ + if not isinstance(columns, list): + columns = list(columns) + for index in get_indexes(engine, table_name): + if index['column_names'] == columns: + return True + return False + + +def add_index(engine, table_name, index_name, idx_columns): """Create an index for given columns. - :param migrate_engine: sqlalchemy engine - :param table_name: name of the table - :param index_name: name of the index - :param idx_columns: tuple with names of columns that will be indexed + :param engine: sqlalchemy engine + :param table_name: name of the table + :param index_name: name of the index + :param idx_columns: tuple with names of columns that will be indexed """ - table = get_table(migrate_engine, table_name) - if not index_exists(migrate_engine, table_name, index_name): + table = get_table(engine, table_name) + if not index_exists(engine, table_name, index_name): index = Index( index_name, *[getattr(table.c, col) for col in idx_columns] ) @@ -765,14 +853,14 @@ raise ValueError("Index '%s' already exists!" % index_name) -def drop_index(migrate_engine, table_name, index_name): +def drop_index(engine, table_name, index_name): """Drop index with given name. - :param migrate_engine: sqlalchemy engine - :param table_name: name of the table - :param index_name: name of the index + :param engine: sqlalchemy engine + :param table_name: name of the table + :param index_name: name of the index """ - table = get_table(migrate_engine, table_name) + table = get_table(engine, table_name) for index in table.indexes: if index.name == index_name: index.drop() @@ -781,24 +869,24 @@ raise ValueError("Index '%s' not found!" % index_name) -def change_index_columns(migrate_engine, table_name, index_name, new_columns): +def change_index_columns(engine, table_name, index_name, new_columns): """Change set of columns that are indexed by given index. - :param migrate_engine: sqlalchemy engine - :param table_name: name of the table - :param index_name: name of the index - :param new_columns: tuple with names of columns that will be indexed + :param engine: sqlalchemy engine + :param table_name: name of the table + :param index_name: name of the index + :param new_columns: tuple with names of columns that will be indexed """ - drop_index(migrate_engine, table_name, index_name) - add_index(migrate_engine, table_name, index_name, new_columns) + drop_index(engine, table_name, index_name) + add_index(engine, table_name, index_name, new_columns) def column_exists(engine, table_name, column): """Check if table has given column. - :param engine: sqlalchemy engine - :param table_name: name of the table - :param column: name of the colmn + :param engine: sqlalchemy engine + :param table_name: name of the table + :param column: name of the colmn """ t = get_table(engine, table_name) return column in t.c diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/base.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/base.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/base.py 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/base.py 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,43 @@ +# Copyright (c) 2016 Openstack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_db.sqlalchemy import enginefacade +from oslo_db.sqlalchemy.test_base import backend_specific # noqa +from oslo_db.sqlalchemy import test_fixtures as db_fixtures +from oslotest import base as test_base + + +@enginefacade.transaction_context_provider +class Context(object): + pass + +context = Context() + + +class DbTestCase(db_fixtures.OpportunisticDBTestMixin, test_base.BaseTestCase): + + def setUp(self): + super(DbTestCase, self).setUp() + + self.engine = enginefacade.writer.get_engine() + self.sessionmaker = enginefacade.writer.get_sessionmaker() + + +class MySQLOpportunisticTestCase(DbTestCase): + FIXTURE = db_fixtures.MySQLOpportunisticFixture + + +class PostgreSQLOpportunisticTestCase(DbTestCase): + FIXTURE = db_fixtures.PostgresqlOpportunisticFixture diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_async_eventlet.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_async_eventlet.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_async_eventlet.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_async_eventlet.py 2017-01-18 14:09:32.000000000 +0000 @@ -24,8 +24,8 @@ from oslo_db import exception as db_exc from oslo_db.sqlalchemy import models -from oslo_db.sqlalchemy import test_base from oslo_db import tests +from oslo_db.tests.sqlalchemy import base as test_base class EventletTestMixin(object): diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_enginefacade.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_enginefacade.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_enginefacade.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_enginefacade.py 2017-01-18 14:09:32.000000000 +0000 @@ -33,7 +33,7 @@ from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import engines as oslo_engines from oslo_db.sqlalchemy import orm -from oslo_db.sqlalchemy import test_base +from oslo_db.tests.sqlalchemy import base as test_base enginefacade.transaction_context_provider(oslo_context.RequestContext) @@ -62,6 +62,7 @@ super(SingletonEngine, self).__init__( "engine", connect=mock.Mock(return_value=connection), + pool=mock.Mock(), url=connection, _assert_connection=connection, **kw @@ -417,6 +418,48 @@ session.mock_calls, self.sessions.element_for_writer(writer).mock_calls) + def test_dispose_pool(self): + facade = enginefacade.transaction_context() + + facade.configure( + connection=self.engine_uri, + ) + + facade.dispose_pool() + self.assertFalse(hasattr(facade._factory, '_writer_engine')) + + facade._factory._start() + facade.dispose_pool() + + self.assertEqual( + facade._factory._writer_engine.pool.mock_calls, + [mock.call.dispose()] + ) + + def test_dispose_pool_w_reader(self): + facade = enginefacade.transaction_context() + + facade.configure( + connection=self.engine_uri, + slave_connection=self.slave_uri + ) + + facade.dispose_pool() + self.assertFalse(hasattr(facade._factory, '_writer_engine')) + self.assertFalse(hasattr(facade._factory, '_reader_engine')) + + facade._factory._start() + facade.dispose_pool() + + self.assertEqual( + facade._factory._writer_engine.pool.mock_calls, + [mock.call.dispose()] + ) + self.assertEqual( + facade._factory._reader_engine.pool.mock_calls, + [mock.call.dispose()] + ) + def test_session_reader_decorator(self): context = oslo_context.RequestContext() @@ -1004,6 +1047,182 @@ getattr, context, 'transaction_ctx' ) + def test_context_found_for_bound_method(self): + context = oslo_context.RequestContext() + + @enginefacade.reader + def go(self, context): + context.session.execute("test") + go(self, context) + + with self._assert_engines() as engines: + with self._assert_makers(engines) as makers: + with self._assert_reader_session(makers) as session: + session.execute("test") + + def test_context_found_for_class_method(self): + context = oslo_context.RequestContext() + + class Spam(object): + @classmethod + @enginefacade.reader + def go(cls, context): + context.session.execute("test") + Spam.go(context) + + with self._assert_engines() as engines: + with self._assert_makers(engines) as makers: + with self._assert_reader_session(makers) as session: + session.execute("test") + + +class PatchFactoryTest(oslo_test_base.BaseTestCase): + + def test_patch_manager(self): + normal_mgr = enginefacade.transaction_context() + normal_mgr.configure(connection="sqlite:///foo.db") + alt_mgr = enginefacade.transaction_context() + alt_mgr.configure(connection="sqlite:///bar.db") + + @normal_mgr.writer + def go1(context): + s1 = context.session + self.assertEqual( + s1.bind.url, "sqlite:///foo.db") + self.assertIs( + s1.bind, + normal_mgr._factory._writer_engine) + + @normal_mgr.writer + def go2(context): + s1 = context.session + + self.assertEqual( + s1.bind.url, + "sqlite:///bar.db") + + self.assertIs( + normal_mgr._factory._writer_engine, + alt_mgr._factory._writer_engine + ) + + def create_engine(sql_connection, **kw): + return mock.Mock(url=sql_connection) + + with mock.patch( + "oslo_db.sqlalchemy.engines.create_engine", create_engine): + context = oslo_context.RequestContext() + go1(context) + reset = normal_mgr.patch_factory(alt_mgr) + go2(context) + reset() + go1(context) + + def test_patch_factory(self): + normal_mgr = enginefacade.transaction_context() + normal_mgr.configure(connection="sqlite:///foo.db") + alt_mgr = enginefacade.transaction_context() + alt_mgr.configure(connection="sqlite:///bar.db") + + @normal_mgr.writer + def go1(context): + s1 = context.session + self.assertEqual( + s1.bind.url, "sqlite:///foo.db") + self.assertIs( + s1.bind, + normal_mgr._factory._writer_engine) + + @normal_mgr.writer + def go2(context): + s1 = context.session + + self.assertEqual( + s1.bind.url, + "sqlite:///bar.db") + + self.assertIs( + normal_mgr._factory._writer_engine, + alt_mgr._factory._writer_engine + ) + + def create_engine(sql_connection, **kw): + return mock.Mock(url=sql_connection) + + with mock.patch( + "oslo_db.sqlalchemy.engines.create_engine", create_engine): + context = oslo_context.RequestContext() + go1(context) + reset = normal_mgr.patch_factory(alt_mgr._factory) + go2(context) + reset() + go1(context) + + def test_patch_engine(self): + normal_mgr = enginefacade.transaction_context() + normal_mgr.configure(connection="sqlite:///foo.db") + + @normal_mgr.writer + def go1(context): + s1 = context.session + self.assertEqual( + s1.bind.url, "sqlite:///foo.db") + self.assertIs( + s1.bind, + normal_mgr._factory._writer_engine) + + @normal_mgr.writer + def go2(context): + s1 = context.session + + self.assertEqual( + s1.bind.url, + "sqlite:///bar.db") + + def create_engine(sql_connection, **kw): + return mock.Mock(url=sql_connection) + + with mock.patch( + "oslo_db.sqlalchemy.engines.create_engine", create_engine): + mock_engine = create_engine("sqlite:///bar.db") + + context = oslo_context.RequestContext() + go1(context) + reset = normal_mgr.patch_engine(mock_engine) + go2(context) + self.assertIs( + normal_mgr._factory._writer_engine, mock_engine) + reset() + go1(context) + + def test_new_manager_from_config(self): + normal_mgr = enginefacade.transaction_context() + normal_mgr.configure( + connection="sqlite://", + sqlite_fk=True, + mysql_sql_mode="FOOBAR", + max_overflow=38 + ) + + normal_mgr._factory._start() + + copied_mgr = normal_mgr.make_new_manager() + + self.assertTrue(normal_mgr._factory._started) + self.assertIsNotNone(normal_mgr._factory._writer_engine) + + self.assertIsNot(copied_mgr._factory, normal_mgr._factory) + self.assertFalse(copied_mgr._factory._started) + copied_mgr._factory._start() + self.assertIsNot( + normal_mgr._factory._writer_engine, + copied_mgr._factory._writer_engine) + + engine_args = copied_mgr._factory._engine_args_for_conf(None) + self.assertTrue(engine_args['sqlite_fk']) + self.assertEqual("FOOBAR", engine_args["mysql_sql_mode"]) + self.assertEqual(38, engine_args["max_overflow"]) + class SynchronousReaderWSlaveMockFacadeTest(MockFacadeTest): synchronous_reader = True @@ -1602,9 +1821,9 @@ session = self.sessionmaker(autocommit=True) self.assertEqual( + [("u1_commit",)], session.query( - self.User.name).order_by(self.User.name).all(), - [("u1_commit",)] + self.User.name).order_by(self.User.name).all() ) def test_replace_scope(self): @@ -1812,5 +2031,21 @@ ) +class TestTransactionFactoryCallback(oslo_test_base.BaseTestCase): + + def test_setup_for_connection_called_with_profiler(self): + context_manager = enginefacade.transaction_context() + context_manager.configure(connection='sqlite://') + hook = mock.Mock() + context_manager.append_on_engine_create(hook) + self.assertEqual( + [hook], context_manager._factory._facade_cfg['on_engine_create']) + + @context_manager.reader + def go(context): + hook.assert_called_once_with(context.session.bind) + + go(oslo_context.RequestContext()) + # TODO(zzzeek): test configuration options, e.g. like # test_sqlalchemy->test_creation_from_config diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_exc_filters.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_exc_filters.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_exc_filters.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_exc_filters.py 2017-01-18 14:09:32.000000000 +0000 @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- +# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -19,13 +21,17 @@ from oslotest import base as oslo_test_base import six import sqlalchemy as sqla +from sqlalchemy.engine import url as sqla_url from sqlalchemy import event +import sqlalchemy.exc +from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import mapper from oslo_db import exception +from oslo_db.sqlalchemy.compat import utils as compat_utils from oslo_db.sqlalchemy import engines from oslo_db.sqlalchemy import exc_filters -from oslo_db.sqlalchemy import test_base +from oslo_db.tests.sqlalchemy import base as test_base from oslo_db.tests import utils as test_utils _TABLE_NAME = '__tmp__test__tmp__' @@ -45,26 +51,26 @@ if isinstance(exception_type, (type, tuple)): self.assertTrue(issubclass(exc.__class__, exception_type)) else: - self.assertEqual(exc.__class__.__name__, exception_type) + self.assertEqual(exception_type, exc.__class__.__name__) if isinstance(message, tuple): self.assertEqual( + [m.lower() + if isinstance(m, six.string_types) else m for m in message], [a.lower() if isinstance(a, six.string_types) else a - for a in exc.orig.args], - [m.lower() - if isinstance(m, six.string_types) else m for m in message] + for a in exc.orig.args] ) else: - self.assertEqual(str(exc.orig).lower(), message.lower()) + self.assertEqual(message.lower(), str(exc.orig).lower()) if sql is not None: if params is not None: if '?' in exc.statement: - self.assertEqual(exc.statement, sql) - self.assertEqual(exc.params, params) + self.assertEqual(sql, exc.statement) + self.assertEqual(params, exc.params) else: - self.assertEqual(exc.statement % exc.params, sql % params) + self.assertEqual(sql % params, exc.statement % exc.params) else: - self.assertEqual(exc.statement, sql) + self.assertEqual(sql, exc.statement) class TestsExceptionFilter(_SQLAExceptionMatcher, oslo_test_base.BaseTestCase): @@ -243,6 +249,203 @@ self.assertEqual("mysqldb has an attribute error", matched.args[0]) +class TestNonExistentConstraint( + _SQLAExceptionMatcher, + test_base.DbTestCase): + + def setUp(self): + super(TestNonExistentConstraint, self).setUp() + + meta = sqla.MetaData(bind=self.engine) + + self.table_1 = sqla.Table( + "resource_foo", meta, + sqla.Column("id", sqla.Integer, primary_key=True), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + self.table_1.create() + + +class TestNonExistentConstraintPostgreSQL( + TestNonExistentConstraint, + test_base.PostgreSQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentConstraint, + self.engine.execute, + sqla.schema.DropConstraint( + sqla.ForeignKeyConstraint(["id"], ["baz.id"], + name="bar_fkey", + table=self.table_1)), + ) + self.assertInnerException( + matched, + "ProgrammingError", + "constraint \"bar_fkey\" of relation " + "\"resource_foo\" does not exist\n", + "ALTER TABLE resource_foo DROP CONSTRAINT bar_fkey", + ) + self.assertEqual("resource_foo", matched.table) + self.assertEqual("bar_fkey", matched.constraint) + + +class TestNonExistentConstraintMySQL( + TestNonExistentConstraint, + test_base.MySQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentConstraint, + self.engine.execute, + sqla.schema.DropConstraint( + sqla.ForeignKeyConstraint(["id"], ["baz.id"], + name="bar_fkey", + table=self.table_1)), + ) + # NOTE(jd) Cannot check precisely with assertInnerException since MySQL + # error are not the same depending on its version… + self.assertIsInstance(matched.inner_exception, + (sqlalchemy.exc.InternalError, + sqlalchemy.exc.OperationalError)) + if matched.table is not None: + self.assertEqual("resource_foo", matched.table) + if matched.constraint is not None: + self.assertEqual("bar_fkey", matched.constraint) + + +class TestNonExistentTable( + _SQLAExceptionMatcher, + test_base.DbTestCase): + + def setUp(self): + super(TestNonExistentTable, self).setUp() + + self.meta = sqla.MetaData(bind=self.engine) + + self.table_1 = sqla.Table( + "foo", self.meta, + sqla.Column("id", sqla.Integer, primary_key=True), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentTable, + self.engine.execute, + sqla.schema.DropTable(self.table_1), + ) + self.assertInnerException( + matched, + "OperationalError", + "no such table: foo", + "\nDROP TABLE foo", + ) + self.assertEqual("foo", matched.table) + + +class TestNonExistentTablePostgreSQL( + TestNonExistentTable, + test_base.PostgreSQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentTable, + self.engine.execute, + sqla.schema.DropTable(self.table_1), + ) + self.assertInnerException( + matched, + "ProgrammingError", + "table \"foo\" does not exist\n", + "\nDROP TABLE foo", + ) + self.assertEqual("foo", matched.table) + + +class TestNonExistentTableMySQL( + TestNonExistentTable, + test_base.MySQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentTable, + self.engine.execute, + sqla.schema.DropTable(self.table_1), + ) + # NOTE(jd) Cannot check precisely with assertInnerException since MySQL + # error are not the same depending on its version… + self.assertIsInstance(matched.inner_exception, + (sqlalchemy.exc.InternalError, + sqlalchemy.exc.OperationalError)) + self.assertEqual("foo", matched.table) + + +class TestNonExistentDatabase( + _SQLAExceptionMatcher, + test_base.DbTestCase): + + def setUp(self): + super(TestNonExistentDatabase, self).setUp() + + url = sqla_url.make_url(str(self.engine.url)) + url.database = 'non_existent_database' + self.url = url + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentDatabase, + engines.create_engine, + sqla_url.make_url( + 'sqlite:////non_existent_dir/non_existent_database') + ) + self.assertIsNone(matched.database) + self.assertInnerException( + matched, + sqlalchemy.exc.OperationalError, + 'unable to open database file', + ) + + +class TestNonExistentDatabaseMySQL( + TestNonExistentDatabase, + test_base.MySQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentDatabase, + engines.create_engine, + self.url + ) + self.assertEqual('non_existent_database', matched.database) + # NOTE(rpodolyaka) cannot check precisely with assertInnerException + # since MySQL errors are not the same depending on its version + self.assertIsInstance( + matched.inner_exception, + (sqlalchemy.exc.InternalError, sqlalchemy.exc.OperationalError), + ) + + +class TestNonExistentDatabasePostgreSQL( + TestNonExistentDatabase, + test_base.PostgreSQLOpportunisticTestCase): + + def test_raise(self): + matched = self.assertRaises( + exception.DBNonExistentDatabase, + engines.create_engine, + self.url + ) + self.assertEqual('non_existent_database', matched.database) + self.assertInnerException( + matched, + sqlalchemy.exc.OperationalError, + 'fatal: database "non_existent_database" does not exist\n', + ) + + class TestReferenceErrorSQLite(_SQLAExceptionMatcher, test_base.DbTestCase): def setUp(self): @@ -376,16 +579,11 @@ self.table_2.insert({'id': 1, 'foo_id': 2}) ) - self.assertInnerException( - matched, - "IntegrityError", - (1452, "Cannot add or update a child row: a " - "foreign key constraint fails (`{0}`.`resource_entity`, " - "CONSTRAINT `foo_fkey` FOREIGN KEY (`foo_id`) REFERENCES " - "`resource_foo` (`id`))".format(self.engine.url.database)), - "INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)", - (1, 2) - ) + # NOTE(jd) Cannot check precisely with assertInnerException since MySQL + # error are not the same depending on its version… + self.assertIsInstance(matched.inner_exception, + sqlalchemy.exc.IntegrityError) + self.assertEqual(matched.inner_exception.orig.args[0], 1452) self.assertEqual("resource_entity", matched.table) self.assertEqual("foo_fkey", matched.constraint) self.assertEqual("foo_id", matched.key) @@ -404,19 +602,11 @@ self.table_2.insert({'id': 1, 'foo_id': 2}) ) - self.assertInnerException( - matched, - "IntegrityError", - ( - 1452, - 'Cannot add or update a child row: a ' - 'foreign key constraint fails ("{0}"."resource_entity", ' - 'CONSTRAINT "foo_fkey" FOREIGN KEY ("foo_id") REFERENCES ' - '"resource_foo" ("id"))'.format(self.engine.url.database) - ), - "INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)", - (1, 2) - ) + # NOTE(jd) Cannot check precisely with assertInnerException since MySQL + # error are not the same depending on its version… + self.assertIsInstance(matched.inner_exception, + sqlalchemy.exc.IntegrityError) + self.assertEqual(matched.inner_exception.orig.args[0], 1452) self.assertEqual("resource_entity", matched.table) self.assertEqual("foo_fkey", matched.constraint) self.assertEqual("foo_id", matched.key) @@ -431,27 +621,134 @@ self.engine.execute, self.table_1.delete() ) - self.assertInnerException( - matched, - "IntegrityError", - ( - 1451, - "Cannot delete or update a parent row: a foreign key " - "constraint fails (`{0}`.`resource_entity`, " - "constraint `foo_fkey` " - "foreign key (`foo_id`) references " - "`resource_foo` (`id`))".format(self.engine.url.database) - ), - "DELETE FROM resource_foo", - (), - ) - + # NOTE(jd) Cannot check precisely with assertInnerException since MySQL + # error are not the same depending on its version… + self.assertIsInstance(matched.inner_exception, + sqlalchemy.exc.IntegrityError) + self.assertEqual(1451, matched.inner_exception.orig.args[0]) self.assertEqual("resource_entity", matched.table) self.assertEqual("foo_fkey", matched.constraint) self.assertEqual("foo_id", matched.key) self.assertEqual("resource_foo", matched.key_table) +class TestExceptionCauseMySQLSavepoint(test_base.MySQLOpportunisticTestCase): + def setUp(self): + super(TestExceptionCauseMySQLSavepoint, self).setUp() + + Base = declarative_base() + + class A(Base): + __tablename__ = 'a' + + id = sqla.Column(sqla.Integer, primary_key=True) + + __table_args__ = {'mysql_engine': 'InnoDB'} + + Base.metadata.create_all(self.engine) + + self.A = A + + def test_cause_for_failed_flush_plus_no_savepoint(self): + session = self.sessionmaker() + + with session.begin(): + session.add(self.A(id=1)) + try: + + with session.begin(): + + try: + with session.begin_nested(): + session.execute("rollback") + session.add(self.A(id=1)) + + # outermost is the failed SAVEPOINT rollback + # from the "with session.begin_nested()" + except exception.DBError as dbe_inner: + + if not compat_utils.sqla_110: + # first "cause" is the failed SAVEPOINT rollback + # from inside of flush(), when it fails + self.assertTrue( + isinstance( + dbe_inner.cause, + exception.DBError + ) + ) + + # second "cause" is then the actual DB duplicate + self.assertTrue( + isinstance( + dbe_inner.cause.cause, + exception.DBDuplicateEntry + ) + ) + else: + # in SQLA 1.1, the rollback() method of Session + # catches the error and repairs the state of the + # session even though the SAVEPOINT was lost; + # the net result here is that one exception is thrown + # instead of two. This is SQLAlchemy ticket #3680 + self.assertTrue( + isinstance( + dbe_inner.cause, + exception.DBDuplicateEntry + ) + ) + + except exception.DBError as dbe_outer: + self.assertTrue( + isinstance( + dbe_outer.cause, + exception.DBDuplicateEntry + ) + ) + + # resets itself afterwards + try: + with session.begin(): + session.add(self.A(id=1)) + except exception.DBError as dbe_outer: + self.assertIsNone(dbe_outer.cause) + + +class TestDBDataErrorSQLite(_SQLAExceptionMatcher, test_base.DbTestCase): + + def setUp(self): + super(TestDBDataErrorSQLite, self).setUp() + + if six.PY3: + self.skip("SQLite database supports unicode value for python3") + + meta = sqla.MetaData(bind=self.engine) + + self.table_1 = sqla.Table( + "resource_foo", meta, + sqla.Column("name", sqla.String), + ) + self.table_1.create() + + def test_raise(self): + + matched = self.assertRaises( + exception.DBDataError, + self.engine.execute, + self.table_1.insert({'name': u'\u2713'.encode('utf-8')}) + ) + + self.assertInnerException( + matched, + "ProgrammingError", + "You must not use 8-bit bytestrings unless you use a " + "text_factory that can interpret 8-bit bytestrings " + "(like text_factory = str). It is highly recommended that " + "you instead just switch your application to Unicode strings.", + "INSERT INTO resource_foo (name) VALUES (?)", + (u'\u2713'.encode('utf-8'),) + ) + + class TestConstraint(TestsExceptionFilter): def test_postgresql(self): matched = self._run_test( @@ -511,6 +808,10 @@ "mysql", '(1062, "Duplicate entry ' '\'2-3\' for key \'uniq_tbl0a0b\'")', expected_value='2-3') + self._run_dupe_constraint_test( + "mysql", + '(1062, "Duplicate entry ' + '\'\' for key \'uniq_tbl0a0b\'")', expected_value='') def test_mysql_mysqlconnector(self): self._run_dupe_constraint_test( @@ -627,7 +928,7 @@ if isinstance(matched, exception.DBError): matched = matched.inner_exception - self.assertEqual(matched.orig.__class__.__name__, expected_dbapi_cls) + self.assertEqual(expected_dbapi_cls, matched.orig.__class__.__name__) def test_mysql_pymysql_deadlock(self): self._run_deadlock_detect_test( @@ -637,11 +938,20 @@ "transaction')" ) + def test_mysql_pymysql_wsrep_deadlock(self): + self._run_deadlock_detect_test( + "mysql", + "(1213, 'WSREP detected deadlock/conflict and aborted the " + "transaction. Try restarting the transaction')", + orig_exception_cls=self.InternalError + ) + def test_mysql_pymysql_galera_deadlock(self): self._run_deadlock_detect_test( "mysql", "(1205, 'Lock wait timeout exceeded; " - "try restarting transaction')" + "try restarting transaction')", + orig_exception_cls=self.InternalError ) def test_mysql_mysqlconnector_deadlock(self): @@ -718,6 +1028,12 @@ '\'resource\' at row 1"', self.DataError) + def test_data_too_long_for_column(self): + self._run_bad_data_test("mysql", + '(1406, "Data too long for column ' + '\'resource\' at row 1"', + self.DataError) + class IntegrationTest(test_base.DbTestCase): """Test an actual error-raising round trips against the database.""" @@ -806,7 +1122,7 @@ foo = self.Foo(counter=sqla.func.imfake(123)) _session.add(foo) matched = self.assertRaises(sqla.exc.OperationalError, _session.flush) - self.assertTrue("no such function" in str(matched)) + self.assertIn("no such function", str(matched)) def test_query_wrapper_operational_error(self): """test an operational error from query.all() raised as-is.""" @@ -818,7 +1134,7 @@ q = _session.query(self.Foo).filter( self.Foo.counter == sqla.func.imfake(123)) matched = self.assertRaises(sqla.exc.OperationalError, q.all) - self.assertTrue("no such function" in str(matched)) + self.assertIn("no such function", str(matched)) class TestDBDisconnected(TestsExceptionFilter): @@ -856,7 +1172,7 @@ with self._fixture(dialect_name, exc_obj, 1, is_disconnect): conn = self.engine.connect() with conn.begin(): - self.assertEqual(conn.scalar(sqla.select([1])), 1) + self.assertEqual(1, conn.scalar(sqla.select([1]))) self.assertFalse(conn.closed) self.assertFalse(conn.invalidated) self.assertTrue(conn.in_transaction()) @@ -869,7 +1185,23 @@ # test implicit execution with self._fixture(dialect_name, exc_obj, 1): - self.assertEqual(self.engine.scalar(sqla.select([1])), 1) + self.assertEqual(1, self.engine.scalar(sqla.select([1]))) + + def test_mariadb_error_1927(self): + for code in [1927]: + self._test_ping_listener_disconnected( + "mysql", + self.InternalError('%d Connection was killed' % code), + is_disconnect=False + ) + + def test_packet_sequence_wrong_error(self): + self._test_ping_listener_disconnected( + "mysql", + self.InternalError( + 'Packet sequence number wrong - got 35 expected 1'), + is_disconnect=False + ) def test_mysql_ping_listener_disconnected(self): for code in [2006, 2013, 2014, 2045, 2055]: @@ -976,7 +1308,7 @@ 2, -1 ) # conn is good - self.assertEqual(conn.scalar(sqla.select([1])), 1) + self.assertEqual(1, conn.scalar(sqla.select([1]))) def test_connect_retry_past_failure(self): conn = self._run_test( @@ -985,7 +1317,7 @@ 2, 3 ) # conn is good - self.assertEqual(conn.scalar(sqla.select([1])), 1) + self.assertEqual(1, conn.scalar(sqla.select([1]))) def test_connect_retry_not_candidate_exception(self): self.assertRaises( @@ -1013,7 +1345,7 @@ 2, -1 ) # conn is good - self.assertEqual(conn.scalar(sqla.select([1])), 1) + self.assertEqual(1, conn.scalar(sqla.select([1]))) def test_db2_error_negative(self): self.assertRaises( @@ -1070,7 +1402,7 @@ self, dialect_name, exc_obj, is_disconnect=True): with self._fixture(dialect_name, exc_obj, 3, is_disconnect): conn = self.engine.connect() - self.assertEqual(conn.scalar(sqla.select([1])), 1) + self.assertEqual(1, conn.scalar(sqla.select([1]))) conn.close() with self._fixture(dialect_name, exc_obj, 1, is_disconnect): diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_fixtures.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_fixtures.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_fixtures.py 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_fixtures.py 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,310 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import os +import testresources +import testscenarios +import unittest + +from oslo_db import exception +from oslo_db.sqlalchemy import enginefacade +from oslo_db.sqlalchemy import provision +from oslo_db.sqlalchemy import test_base as legacy_test_base +from oslo_db.sqlalchemy import test_fixtures +from oslotest import base as oslo_test_base + +start_dir = os.path.dirname(__file__) + + +class BackendSkipTest(oslo_test_base.BaseTestCase): + + def test_skip_no_dbapi(self): + + class FakeDatabaseOpportunisticFixture( + test_fixtures.OpportunisticDbFixture): + DRIVER = 'postgresql' + + class SomeTest(test_fixtures.OpportunisticDBTestMixin, + oslo_test_base.BaseTestCase): + FIXTURE = FakeDatabaseOpportunisticFixture + + def runTest(self): + pass + + st = SomeTest() + + # patch in replacement lookup dictionaries to avoid + # leaking from/to other tests + with mock.patch( + "oslo_db.sqlalchemy.provision." + "Backend.backends_by_database_type", { + "postgresql": + provision.Backend("postgresql", "postgresql://")}): + st._database_resources = {} + st._db_not_available = {} + st._schema_resources = {} + + with mock.patch( + "sqlalchemy.create_engine", + mock.Mock(side_effect=ImportError())): + + self.assertEqual([], st.resources) + + ex = self.assertRaises( + self.skipException, + st.setUp + ) + + self.assertEqual( + "Backend 'postgresql' is unavailable: No DBAPI installed", + str(ex) + ) + + def test_skip_no_such_backend(self): + + class FakeDatabaseOpportunisticFixture( + test_fixtures.OpportunisticDbFixture): + DRIVER = 'postgresql+nosuchdbapi' + + class SomeTest(test_fixtures.OpportunisticDBTestMixin, + oslo_test_base.BaseTestCase): + + FIXTURE = FakeDatabaseOpportunisticFixture + + def runTest(self): + pass + + st = SomeTest() + + ex = self.assertRaises( + self.skipException, + st.setUp + ) + + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: No such backend", + str(ex) + ) + + def test_skip_no_dbapi_legacy(self): + + class FakeDatabaseOpportunisticFixture( + legacy_test_base.DbFixture): + DRIVER = 'postgresql' + + class SomeTest(legacy_test_base.DbTestCase): + FIXTURE = FakeDatabaseOpportunisticFixture + + def runTest(self): + pass + + st = SomeTest() + + # patch in replacement lookup dictionaries to avoid + # leaking from/to other tests + with mock.patch( + "oslo_db.sqlalchemy.provision." + "Backend.backends_by_database_type", { + "postgresql": + provision.Backend("postgresql", "postgresql://")}): + st._database_resources = {} + st._db_not_available = {} + st._schema_resources = {} + + with mock.patch( + "sqlalchemy.create_engine", + mock.Mock(side_effect=ImportError())): + + self.assertEqual([], st.resources) + + ex = self.assertRaises( + self.skipException, + st.setUp + ) + + self.assertEqual( + "Backend 'postgresql' is unavailable: No DBAPI installed", + str(ex) + ) + + def test_skip_no_such_backend_legacy(self): + + class FakeDatabaseOpportunisticFixture( + legacy_test_base.DbFixture): + DRIVER = 'postgresql+nosuchdbapi' + + class SomeTest(legacy_test_base.DbTestCase): + + FIXTURE = FakeDatabaseOpportunisticFixture + + def runTest(self): + pass + + st = SomeTest() + + ex = self.assertRaises( + self.skipException, + st.setUp + ) + + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: No such backend", + str(ex) + ) + + +class EnginefacadeIntegrationTest(oslo_test_base.BaseTestCase): + def test_db_fixture(self): + normal_mgr = enginefacade.transaction_context() + normal_mgr.configure( + connection="sqlite://", + sqlite_fk=True, + mysql_sql_mode="FOOBAR", + max_overflow=38 + ) + + class MyFixture(test_fixtures.OpportunisticDbFixture): + def get_enginefacade(self): + return normal_mgr + + test = mock.Mock(SCHEMA_SCOPE=None) + fixture = MyFixture(test=test) + resources = fixture._get_resources() + + testresources.setUpResources(test, resources, None) + self.addCleanup( + testresources.tearDownResources, + test, resources, None + ) + fixture.setUp() + self.addCleanup(fixture.cleanUp) + + self.assertTrue(normal_mgr._factory._started) + + test.engine = normal_mgr.writer.get_engine() + self.assertEqual("sqlite://", str(test.engine.url)) + self.assertIs(test.engine, normal_mgr._factory._writer_engine) + engine_args = normal_mgr._factory._engine_args_for_conf(None) + self.assertTrue(engine_args['sqlite_fk']) + self.assertEqual("FOOBAR", engine_args["mysql_sql_mode"]) + self.assertEqual(38, engine_args["max_overflow"]) + + fixture.cleanUp() + fixture._clear_cleanups() # so the real cleanUp works + self.assertFalse(normal_mgr._factory._started) + + +class LegacyBaseClassTest(oslo_test_base.BaseTestCase): + def test_new_db_is_provisioned_by_default_pg(self): + self._test_new_db_is_provisioned_by_default( + legacy_test_base.PostgreSQLOpportunisticTestCase + ) + + def test_new_db_is_provisioned_by_default_mysql(self): + self._test_new_db_is_provisioned_by_default( + legacy_test_base.MySQLOpportunisticTestCase + ) + + def _test_new_db_is_provisioned_by_default(self, base_cls): + try: + provision.DatabaseResource(base_cls.FIXTURE.DRIVER) + except exception.BackendNotAvailable: + self.skip("Backend %s is not available" % + base_cls.FIXTURE.DRIVER) + + class SomeTest(base_cls): + def runTest(self): + pass + st = SomeTest() + + db_resource = dict(st.resources)['db'] + self.assertTrue(db_resource.provision_new_database) + + +class TestLoadHook(unittest.TestCase): + """Test the 'load_tests' hook supplied by test_base. + + The purpose of this loader is to organize tests into an + OptimisingTestSuite using the standard unittest load_tests hook. + The hook needs to detect if it is being invoked at the module + level or at the package level. It has to behave completely differently + in these two cases. + + """ + + def test_module_level(self): + load_tests = test_fixtures.optimize_module_test_loader() + + loader = unittest.TestLoader() + + found_tests = loader.discover(start_dir, pattern="test_fixtures.py") + new_loader = load_tests(loader, found_tests, "test_fixtures.py") + + self.assertTrue( + isinstance(new_loader, testresources.OptimisingTestSuite) + ) + + actual_tests = unittest.TestSuite( + testscenarios.generate_scenarios(found_tests) + ) + + self.assertEqual( + new_loader.countTestCases(), actual_tests.countTestCases() + ) + + def test_package_level(self): + self._test_package_level(test_fixtures.optimize_package_test_loader) + + def test_package_level_legacy(self): + self._test_package_level(legacy_test_base.optimize_db_test_loader) + + def _test_package_level(self, fn): + load_tests = fn( + os.path.join(start_dir, "__init__.py")) + + loader = unittest.TestLoader() + + new_loader = load_tests( + loader, unittest.suite.TestSuite(), "test_fixtures.py") + + self.assertTrue( + isinstance(new_loader, testresources.OptimisingTestSuite) + ) + + actual_tests = unittest.TestSuite( + testscenarios.generate_scenarios( + loader.discover(start_dir, pattern="test_fixtures.py")) + ) + + self.assertEqual( + new_loader.countTestCases(), actual_tests.countTestCases() + ) + + +class TestWScenarios(unittest.TestCase): + """a 'do nothing' test suite. + + Should generate exactly four tests when testscenarios is used. + + """ + + def test_one(self): + pass + + def test_two(self): + pass + + scenarios = [ + ('scenario1', dict(scenario='scenario 1')), + ('scenario2', dict(scenario='scenario 2')) + ] diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_handle_error.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_handle_error.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_handle_error.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_handle_error.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,194 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Test the compatibility layer for the handle_error() event. - -This event is added as of SQLAlchemy 0.9.7; oslo_db provides a compatibility -layer for prior SQLAlchemy versions. - -""" - -import mock -from oslotest import base as test_base -import sqlalchemy as sqla -from sqlalchemy.sql import column -from sqlalchemy.sql import literal -from sqlalchemy.sql import select -from sqlalchemy.types import Integer -from sqlalchemy.types import TypeDecorator - -from oslo_db.sqlalchemy.compat import handle_error -from oslo_db.sqlalchemy.compat import utils -from oslo_db.tests import utils as test_utils - - -class MyException(Exception): - pass - - -class ExceptionReraiseTest(test_base.BaseTestCase): - - def setUp(self): - super(ExceptionReraiseTest, self).setUp() - - self.engine = engine = sqla.create_engine("sqlite://") - self.addCleanup(engine.dispose) - - def _fixture(self): - engine = self.engine - - def err(context): - if "ERROR ONE" in str(context.statement): - raise MyException("my exception") - handle_error(engine, err) - - def test_exception_event_altered(self): - self._fixture() - - with mock.patch.object(self.engine.dialect.execution_ctx_cls, - "handle_dbapi_exception") as patched: - - matchee = self.assertRaises( - MyException, - self.engine.execute, "SELECT 'ERROR ONE' FROM I_DONT_EXIST" - ) - self.assertEqual(1, patched.call_count) - self.assertEqual("my exception", matchee.args[0]) - - def test_exception_event_non_altered(self): - self._fixture() - - with mock.patch.object(self.engine.dialect.execution_ctx_cls, - "handle_dbapi_exception") as patched: - - self.assertRaises( - sqla.exc.DBAPIError, - self.engine.execute, "SELECT 'ERROR TWO' FROM I_DONT_EXIST" - ) - self.assertEqual(1, patched.call_count) - - def test_is_disconnect_not_interrupted(self): - self._fixture() - - with test_utils.nested( - mock.patch.object( - self.engine.dialect.execution_ctx_cls, - "handle_dbapi_exception" - ), - mock.patch.object( - self.engine.dialect, "is_disconnect", - lambda *args: True - ) - ) as (handle_dbapi_exception, is_disconnect): - with self.engine.connect() as conn: - self.assertRaises( - MyException, - conn.execute, "SELECT 'ERROR ONE' FROM I_DONT_EXIST" - ) - self.assertEqual(1, handle_dbapi_exception.call_count) - self.assertTrue(conn.invalidated) - - def test_no_is_disconnect_not_invalidated(self): - self._fixture() - - with test_utils.nested( - mock.patch.object( - self.engine.dialect.execution_ctx_cls, - "handle_dbapi_exception" - ), - mock.patch.object( - self.engine.dialect, "is_disconnect", - lambda *args: False - ) - ) as (handle_dbapi_exception, is_disconnect): - with self.engine.connect() as conn: - self.assertRaises( - MyException, - conn.execute, "SELECT 'ERROR ONE' FROM I_DONT_EXIST" - ) - self.assertEqual(1, handle_dbapi_exception.call_count) - self.assertFalse(conn.invalidated) - - def test_exception_event_ad_hoc_context(self): - engine = self.engine - - nope = MyException("nope") - - class MyType(TypeDecorator): - impl = Integer - - def process_bind_param(self, value, dialect): - raise nope - - listener = mock.Mock(return_value=None) - handle_error(engine, listener) - - self.assertRaises( - sqla.exc.StatementError, - engine.execute, - select([1]).where(column('foo') == literal('bar', MyType)) - ) - - ctx = listener.mock_calls[0][1][0] - self.assertTrue(ctx.statement.startswith("SELECT 1 ")) - self.assertIs(ctx.is_disconnect, False) - self.assertIs(ctx.original_exception, nope) - - def _test_alter_disconnect(self, orig_error, evt_value): - engine = self.engine - - def evt(ctx): - ctx.is_disconnect = evt_value - handle_error(engine, evt) - - # if we are under sqla 0.9.7, and we are expecting to take - # an "is disconnect" exception and make it not a disconnect, - # that isn't supported b.c. the wrapped handler has already - # done the invalidation. - expect_failure = not utils.sqla_097 and orig_error and not evt_value - - with mock.patch.object(engine.dialect, - "is_disconnect", - mock.Mock(return_value=orig_error)): - - with engine.connect() as c: - conn_rec = c.connection._connection_record - try: - c.execute("SELECT x FROM nonexistent") - assert False - except sqla.exc.StatementError as st: - self.assertFalse(expect_failure) - - # check the exception's invalidation flag - self.assertEqual(st.connection_invalidated, evt_value) - - # check the Connection object's invalidation flag - self.assertEqual(c.invalidated, evt_value) - - # this is the ConnectionRecord object; it's invalidated - # when its .connection member is None - self.assertEqual(conn_rec.connection is None, evt_value) - - except NotImplementedError as ne: - self.assertTrue(expect_failure) - self.assertEqual( - str(ne), - "Can't reset 'disconnect' status of exception once it " - "is set with this version of SQLAlchemy") - - def test_alter_disconnect_to_true(self): - self._test_alter_disconnect(False, True) - self._test_alter_disconnect(True, True) - - def test_alter_disconnect_to_false(self): - self._test_alter_disconnect(True, False) - self._test_alter_disconnect(False, False) diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migrate_cli.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migrate_cli.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migrate_cli.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migrate_cli.py 2017-01-18 14:09:32.000000000 +0000 @@ -285,11 +285,11 @@ def test_upgrade_right_order(self): results = self.migration_manager.upgrade(None) - self.assertEqual(results, [100, 200]) + self.assertEqual([100, 200], results) def test_downgrade_right_order(self): results = self.migration_manager.downgrade(None) - self.assertEqual(results, [100, 0]) + self.assertEqual([100, 0], results) def test_upgrade_does_not_go_too_far(self): self.first_ext.obj.has_revision.return_value = True @@ -314,7 +314,7 @@ self.second_ext.obj.has_revision.return_value = False # upgrade to a specific non-existent revision should fail - self.assertRaises(exception.DbMigrationError, + self.assertRaises(exception.DBMigrationError, self.migration_manager.upgrade, 100) # upgrade to the "head" should succeed @@ -332,7 +332,7 @@ self.second_ext.obj.has_revision.return_value = False # upgrade to a specific non-existent revision should fail - self.assertRaises(exception.DbMigrationError, + self.assertRaises(exception.DBMigrationError, self.migration_manager.downgrade, 100) # downgrade to the "base" should succeed diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migration_common.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migration_common.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migration_common.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migration_common.py 2017-01-18 14:09:32.000000000 +0000 @@ -24,7 +24,7 @@ from oslo_db import exception as db_exception from oslo_db.sqlalchemy import migration -from oslo_db.sqlalchemy import test_base +from oslo_db.tests.sqlalchemy import base as test_base from oslo_db.tests import utils as test_utils @@ -56,7 +56,7 @@ def test_find_migrate_repo_path_not_found(self): self.assertRaises( - db_exception.DbMigrationError, + db_exception.DBMigrationError, migration._find_migrate_repo, "/foo/bar/", ) @@ -65,7 +65,7 @@ def test_find_migrate_repo_called_once(self): my_repository = migration._find_migrate_repo(self.path) self.repository.assert_called_once_with(self.path) - self.assertEqual(my_repository, self.return_value) + self.assertEqual(self.return_value, my_repository) def test_find_migrate_repo_called_few_times(self): repo1 = migration._find_migrate_repo(self.path) @@ -82,14 +82,36 @@ version = migration.db_version_control( self.engine, self.path, self.test_version) - self.assertEqual(version, self.test_version) + self.assertEqual(self.test_version, version) mock_version_control.assert_called_once_with( self.engine, self.return_value, self.test_version) + @mock.patch.object(migration, '_find_migrate_repo') + @mock.patch.object(versioning_api, 'version_control') + def test_db_version_control_version_less_than_actual_version( + self, mock_version_control, mock_find_repo): + mock_find_repo.return_value = self.return_value + mock_version_control.side_effect = (migrate_exception. + DatabaseAlreadyControlledError) + self.assertRaises(db_exception.DBMigrationError, + migration.db_version_control, self.engine, + self.path, self.test_version - 1) + + @mock.patch.object(migration, '_find_migrate_repo') + @mock.patch.object(versioning_api, 'version_control') + def test_db_version_control_version_greater_than_actual_version( + self, mock_version_control, mock_find_repo): + mock_find_repo.return_value = self.return_value + mock_version_control.side_effect = (migrate_exception. + InvalidVersionError) + self.assertRaises(db_exception.DBMigrationError, + migration.db_version_control, self.engine, + self.path, self.test_version + 1) + def test_db_version_return(self): ret_val = migration.db_version(self.engine, self.path, self.init_version) - self.assertEqual(ret_val, self.test_version) + self.assertEqual(self.test_version, ret_val) def test_db_version_raise_not_controlled_error_first(self): with mock.patch.object(migration, 'db_version_control') as mock_ver: @@ -100,7 +122,7 @@ ret_val = migration.db_version(self.engine, self.path, self.init_version) - self.assertEqual(ret_val, self.test_version) + self.assertEqual(self.test_version, ret_val) mock_ver.assert_called_once_with(self.engine, self.path, version=self.init_version) @@ -113,7 +135,7 @@ mock_meta.return_value = my_meta self.assertRaises( - db_exception.DbMigrationError, migration.db_version, + db_exception.DBMigrationError, migration.db_version, self.engine, self.path, self.init_version) @mock.patch.object(versioning_api, 'version_control') @@ -131,7 +153,7 @@ self.init_version) def test_db_sync_wrong_version(self): - self.assertRaises(db_exception.DbMigrationError, + self.assertRaises(db_exception.DBMigrationError, migration.db_sync, self.engine, self.path, 'foo') def test_db_sync_upgrade(self): diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migrations.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migrations.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_migrations.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_migrations.py 2017-01-18 14:09:32.000000000 +0000 @@ -23,8 +23,8 @@ import sqlalchemy.ext.declarative as sa_decl from oslo_db import exception as exc -from oslo_db.sqlalchemy import test_base from oslo_db.sqlalchemy import test_migrations as migrate +from oslo_db.tests.sqlalchemy import base as test_base class TestWalkVersions(test.BaseTestCase, migrate.WalkVersionsMixin): @@ -47,6 +47,10 @@ self.migration_api.db_version.assert_called_with( self.engine, self.REPOSITORY) + @staticmethod + def _fake_upgrade_boom(*args, **kwargs): + raise exc.DBMigrationError("boom") + def test_migrate_up_fail(self): version = 141 self.migration_api.db_version.return_value = version @@ -56,9 +60,9 @@ with mock.patch.object(self.migration_api, 'upgrade', - side_effect=exc.DbMigrationError): + side_effect=self._fake_upgrade_boom): log = self.useFixture(fixtures.FakeLogger()) - self.assertRaises(exc.DbMigrationError, self.migrate_up, version) + self.assertRaises(exc.DBMigrationError, self.migrate_up, version) self.assertEqual(expected_output, log.output) def test_migrate_up_with_data(self): @@ -111,10 +115,10 @@ int(self.REPOSITORY.latest) + 1) upgraded = [mock.call(v, with_data=True) for v in versions] - self.assertEqual(self.migrate_up.call_args_list, upgraded) + self.assertEqual(upgraded, self.migrate_up.call_args_list) downgraded = [mock.call(v - 1) for v in reversed(versions)] - self.assertEqual(self.migrate_down.call_args_list, downgraded) + self.assertEqual(downgraded, self.migrate_down.call_args_list) @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_up') @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_down') @@ -139,7 +143,7 @@ downgraded_2.append(mock.call(v - 1)) downgraded_2.append(mock.call(v - 1)) downgraded = downgraded_1 + downgraded_2 - self.assertEqual(self.migrate_down.call_args_list, downgraded) + self.assertEqual(downgraded, self.migrate_down.call_args_list) @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_up') @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_down') @@ -159,7 +163,7 @@ self.assertEqual(upgraded, self.migrate_up.call_args_list) downgraded = [mock.call(v - 1, with_data=True) for v in versions] - self.assertEqual(self.migrate_down.call_args_list, downgraded) + self.assertEqual(downgraded, self.migrate_down.call_args_list) @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_up') @mock.patch.object(migrate.WalkVersionsMixin, 'migrate_down') @@ -200,6 +204,7 @@ name='testenum'), server_default="first"), sa.Column('variant', sa.BigInteger()), + sa.Column('variant2', sa.BigInteger(), server_default='0'), sa.Column('fk_check', sa.String(36), nullable=False), sa.UniqueConstraint('spam', 'eggs', name='uniq_cons'), ) @@ -230,6 +235,8 @@ server_default="first") variant = sa.Column(sa.BigInteger().with_variant( sa.Integer(), 'sqlite')) + variant2 = sa.Column(sa.BigInteger().with_variant( + sa.Integer(), 'sqlite'), server_default='0') bar = sa.Column('bar', sa.Numeric(10, 5)) class ModelThatShouldNotBeCompared(BASE): @@ -504,7 +511,7 @@ ) for cmd, fk, tname, fk_info in diffs ] - self.assertEqual(diffs, compare_to) + self.assertEqual(compare_to, diffs) def test_fk_added(self): self._fk_added_fixture() diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_models.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_models.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_models.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_models.py 2017-01-18 14:09:32.000000000 +0000 @@ -14,14 +14,17 @@ # under the License. import collections +import datetime +import mock from oslotest import base as oslo_test from sqlalchemy import Column from sqlalchemy import Integer, String +from sqlalchemy import event from sqlalchemy.ext.declarative import declarative_base from oslo_db.sqlalchemy import models -from oslo_db.sqlalchemy import test_base +from oslo_db.tests.sqlalchemy import base as test_base BASE = declarative_base() @@ -52,13 +55,13 @@ def test_modelbase_set(self): self.mb['world'] = 'hello' - self.assertEqual(self.mb['world'], 'hello') + self.assertEqual('hello', self.mb['world']) def test_modelbase_update(self): h = {'a': '1', 'b': '2'} self.mb.update(h) for key in h.keys(): - self.assertEqual(self.mb[key], h[key]) + self.assertEqual(h[key], self.mb[key]) def test_modelbase_contains(self): mb = models.ModelBase() @@ -66,9 +69,9 @@ mb.update(h) for key in h.keys(): # Test 'in' syntax (instead of using .assertIn) - self.assertTrue(key in mb) + self.assertIn(key, mb) - self.assertFalse('non-existent-key' in mb) + self.assertNotIn('non-existent-key', mb) def test_modelbase_contains_exc(self): class ErrorModel(models.ModelBase): @@ -79,7 +82,7 @@ model = ErrorModel() model.update({'attr': 5}) - self.assertTrue('attr' in model) + self.assertIn('attr', model) self.assertRaises(ValueError, lambda: 'bug' in model) def test_modelbase_items_iteritems(self): @@ -92,8 +95,8 @@ 'b': '2', } self.ekm.update(h) - self.assertEqual(dict(self.ekm.items()), expected) - self.assertEqual(dict(self.ekm.iteritems()), expected) + self.assertEqual(expected, dict(self.ekm.items())) + self.assertEqual(expected, dict(self.ekm.iteritems())) def test_modelbase_dict(self): h = {'a': '1', 'b': '2'} @@ -105,7 +108,7 @@ 'b': '2', } self.ekm.update(h) - self.assertEqual(dict(self.ekm), expected) + self.assertEqual(expected, dict(self.ekm)) def test_modelbase_iter(self): expected = { @@ -125,12 +128,11 @@ self.assertEqual(len(expected), found_items) def test_modelbase_keys(self): - self.assertEqual(set(self.ekm.keys()), - set(('id', 'smth', 'name'))) + self.assertEqual(set(('id', 'smth', 'name')), set(self.ekm.keys())) self.ekm.update({'a': '1', 'b': '2'}) - self.assertEqual(set(self.ekm.keys()), - set(('a', 'b', 'id', 'smth', 'name'))) + self.assertEqual(set(('a', 'b', 'id', 'smth', 'name')), + set(self.ekm.keys())) def test_modelbase_several_iters(self): mb = ExtraKeysModel() @@ -138,22 +140,23 @@ it2 = iter(mb) self.assertFalse(it1 is it2) - self.assertEqual(dict(it1), dict(mb)) - self.assertEqual(dict(it2), dict(mb)) + self.assertEqual(dict(mb), dict(it1)) + self.assertEqual(dict(mb), dict(it2)) def test_extra_keys_empty(self): """Test verifies that by default extra_keys return empty list.""" - self.assertEqual(self.mb._extra_keys, []) + self.assertEqual([], self.mb._extra_keys) def test_extra_keys_defined(self): """Property _extra_keys will return list with attributes names.""" - self.assertEqual(self.ekm._extra_keys, ['name']) + self.assertEqual(['name'], self.ekm._extra_keys) def test_model_with_extra_keys(self): data = dict(self.ekm) - self.assertEqual(data, {'smth': None, - 'id': None, - 'name': 'NAME'}) + self.assertEqual({'smth': None, + 'id': None, + 'name': 'NAME'}, + data) class ExtraKeysModel(BASE, models.ModelBase): @@ -179,3 +182,60 @@ for method in methods: self.assertTrue(hasattr(models.TimestampMixin, method), "Method %s() is not found" % method) + + +class SoftDeletedModel(BASE, models.ModelBase, models.SoftDeleteMixin): + __tablename__ = 'test_model_soft_deletes' + + id = Column('id', Integer, primary_key=True) + smth = Column('smth', String(255)) + + +class SoftDeleteMixinTest(test_base.DbTestCase): + def setUp(self): + super(SoftDeleteMixinTest, self).setUp() + + t = BASE.metadata.tables['test_model_soft_deletes'] + t.create(self.engine) + self.addCleanup(t.drop, self.engine) + + self.session = self.sessionmaker(autocommit=False) + self.addCleanup(self.session.close) + + @mock.patch('oslo_utils.timeutils.utcnow') + def test_soft_delete(self, mock_utcnow): + dt = datetime.datetime.utcnow().replace(microsecond=0) + mock_utcnow.return_value = dt + + m = SoftDeletedModel(id=123456, smth='test') + self.session.add(m) + self.session.commit() + self.assertEqual(0, m.deleted) + self.assertIs(None, m.deleted_at) + + m.soft_delete(self.session) + self.assertEqual(123456, m.deleted) + self.assertIs(dt, m.deleted_at) + + def test_soft_delete_coerce_deleted_to_integer(self): + def listener(conn, cur, stmt, params, context, executemany): + if 'insert' in stmt.lower(): # ignore SELECT 1 and BEGIN + self.assertNotIn('False', str(params)) + + event.listen(self.engine, 'before_cursor_execute', listener) + self.addCleanup(event.remove, + self.engine, 'before_cursor_execute', listener) + + m = SoftDeletedModel(id=1, smth='test', deleted=False) + self.session.add(m) + self.session.commit() + + def test_deleted_set_to_null(self): + m = SoftDeletedModel(id=123456, smth='test') + self.session.add(m) + self.session.commit() + + m.deleted = None + self.session.commit() + + self.assertIsNone(m.deleted) diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_options.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_options.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_options.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_options.py 2017-01-18 14:09:32.000000000 +0000 @@ -39,14 +39,14 @@ sql_connection_trace=True """]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.connection, 'x://y.z') - self.assertEqual(self.conf.database.min_pool_size, 10) - self.assertEqual(self.conf.database.max_pool_size, 20) - self.assertEqual(self.conf.database.max_retries, 30) - self.assertEqual(self.conf.database.retry_interval, 40) - self.assertEqual(self.conf.database.max_overflow, 50) - self.assertEqual(self.conf.database.connection_debug, 60) - self.assertEqual(self.conf.database.connection_trace, True) + self.assertEqual('x://y.z', self.conf.database.connection) + self.assertEqual(10, self.conf.database.min_pool_size) + self.assertEqual(20, self.conf.database.max_pool_size) + self.assertEqual(30, self.conf.database.max_retries) + self.assertEqual(40, self.conf.database.retry_interval) + self.assertEqual(50, self.conf.database.max_overflow) + self.assertEqual(60, self.conf.database.connection_debug) + self.assertEqual(True, self.conf.database.connection_trace) def test_session_parameters(self): path = self.create_tempfiles([["tmp", b"""[database] @@ -61,15 +61,15 @@ pool_timeout=7 """]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.connection, 'x://y.z') - self.assertEqual(self.conf.database.min_pool_size, 10) - self.assertEqual(self.conf.database.max_pool_size, 20) - self.assertEqual(self.conf.database.max_retries, 30) - self.assertEqual(self.conf.database.retry_interval, 40) - self.assertEqual(self.conf.database.max_overflow, 50) - self.assertEqual(self.conf.database.connection_debug, 60) - self.assertEqual(self.conf.database.connection_trace, True) - self.assertEqual(self.conf.database.pool_timeout, 7) + self.assertEqual('x://y.z', self.conf.database.connection) + self.assertEqual(10, self.conf.database.min_pool_size) + self.assertEqual(20, self.conf.database.max_pool_size) + self.assertEqual(30, self.conf.database.max_retries) + self.assertEqual(40, self.conf.database.retry_interval) + self.assertEqual(50, self.conf.database.max_overflow) + self.assertEqual(60, self.conf.database.connection_debug) + self.assertEqual(True, self.conf.database.connection_trace) + self.assertEqual(7, self.conf.database.pool_timeout) def test_dbapi_database_deprecated_parameters(self): path = self.create_tempfiles([['tmp', b'[DATABASE]\n' @@ -83,14 +83,14 @@ b'sqlalchemy_pool_timeout=5\n' ]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.connection, 'fake_connection') - self.assertEqual(self.conf.database.idle_timeout, 100) - self.assertEqual(self.conf.database.min_pool_size, 99) - self.assertEqual(self.conf.database.max_pool_size, 199) - self.assertEqual(self.conf.database.max_retries, 22) - self.assertEqual(self.conf.database.retry_interval, 17) - self.assertEqual(self.conf.database.max_overflow, 101) - self.assertEqual(self.conf.database.pool_timeout, 5) + self.assertEqual('fake_connection', self.conf.database.connection) + self.assertEqual(100, self.conf.database.idle_timeout) + self.assertEqual(99, self.conf.database.min_pool_size) + self.assertEqual(199, self.conf.database.max_pool_size) + self.assertEqual(22, self.conf.database.max_retries) + self.assertEqual(17, self.conf.database.retry_interval) + self.assertEqual(101, self.conf.database.max_overflow) + self.assertEqual(5, self.conf.database.pool_timeout) def test_dbapi_database_deprecated_parameters_sql(self): path = self.create_tempfiles([['tmp', b'[sql]\n' @@ -98,8 +98,8 @@ b'idle_timeout=99\n' ]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.connection, 'test_sql_connection') - self.assertEqual(self.conf.database.idle_timeout, 99) + self.assertEqual('test_sql_connection', self.conf.database.connection) + self.assertEqual(99, self.conf.database.idle_timeout) def test_deprecated_dbapi_parameters(self): path = self.create_tempfiles([['tmp', b'[DEFAULT]\n' @@ -107,7 +107,7 @@ ]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.backend, 'test_123') + self.assertEqual('test_123', self.conf.database.backend) def test_dbapi_parameters(self): path = self.create_tempfiles([['tmp', b'[database]\n' @@ -115,7 +115,7 @@ ]])[0] self.conf(['--config-file', path]) - self.assertEqual(self.conf.database.backend, 'test_123') + self.assertEqual('test_123', self.conf.database.backend) def test_set_defaults(self): conf = cfg.ConfigOpts() diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_provision.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_provision.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_provision.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_provision.py 2017-01-18 14:09:32.000000000 +0000 @@ -10,14 +10,21 @@ # License for the specific language governing permissions and limitations # under the License. +import mock +import os + from oslotest import base as oslo_test_base +from sqlalchemy import exc as sa_exc from sqlalchemy import inspect from sqlalchemy import schema from sqlalchemy import types from oslo_db import exception +from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import provision -from oslo_db.sqlalchemy import test_base +from oslo_db.sqlalchemy import test_fixtures +from oslo_db.sqlalchemy import utils +from oslo_db.tests.sqlalchemy import base as test_base class DropAllObjectsTest(test_base.DbTestCase): @@ -64,7 +71,8 @@ set(insp.get_table_names()) ) - self.db.backend.drop_all_objects(self.engine) + self._get_default_provisioned_db().\ + backend.drop_all_objects(self.engine) insp = inspect(self.engine) self.assertEqual( @@ -73,6 +81,62 @@ ) +class BackendNotAvailableTest(oslo_test_base.BaseTestCase): + def test_no_dbapi(self): + backend = provision.Backend( + "postgresql", "postgresql+nosuchdbapi://hostname/dsn") + + with mock.patch( + "sqlalchemy.create_engine", + mock.Mock(side_effect=ImportError("nosuchdbapi"))): + + # NOTE(zzzeek): Call and test the _verify function twice, as it + # exercises a different code path on subsequent runs vs. + # the first run + ex = self.assertRaises( + exception.BackendNotAvailable, + backend._verify) + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: " + "No DBAPI installed", str(ex)) + + ex = self.assertRaises( + exception.BackendNotAvailable, + backend._verify) + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: " + "No DBAPI installed", str(ex)) + + def test_cant_connect(self): + backend = provision.Backend( + "postgresql", "postgresql+nosuchdbapi://hostname/dsn") + + with mock.patch( + "sqlalchemy.create_engine", + mock.Mock(return_value=mock.Mock(connect=mock.Mock( + side_effect=sa_exc.OperationalError( + "can't connect", None, None)) + )) + ): + + # NOTE(zzzeek): Call and test the _verify function twice, as it + # exercises a different code path on subsequent runs vs. + # the first run + ex = self.assertRaises( + exception.BackendNotAvailable, + backend._verify) + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: " + "Could not connect", str(ex)) + + ex = self.assertRaises( + exception.BackendNotAvailable, + backend._verify) + self.assertEqual( + "Backend 'postgresql+nosuchdbapi' is unavailable: " + "Could not connect", str(ex)) + + class MySQLDropAllObjectsTest( DropAllObjectsTest, test_base.MySQLOpportunisticTestCase): pass @@ -109,20 +173,22 @@ def _run_test(self): try: - database_resource = provision.DatabaseResource(self.DRIVER) + database_resource = provision.DatabaseResource( + self.DRIVER, provision_new_database=True) except exception.BackendNotAvailable: self.skip("database not available") schema_resource = provision.SchemaResource( database_resource, self._gen_schema) - transaction_resource = provision.TransactionResource( - database_resource, schema_resource) - engine = transaction_resource.getResource() + schema = schema_resource.getResource() + + conn = schema.database.engine.connect() + engine = utils.NonCommittingEngine(conn) with engine.connect() as conn: rows = conn.execute(self.test_table.select()) - self.assertEqual(rows.fetchall(), []) + self.assertEqual([], rows.fetchall()) trans = conn.begin() conn.execute( @@ -132,7 +198,7 @@ trans.rollback() rows = conn.execute(self.test_table.select()) - self.assertEqual(rows.fetchall(), []) + self.assertEqual([], rows.fetchall()) trans = conn.begin() conn.execute( @@ -142,9 +208,10 @@ trans.commit() rows = conn.execute(self.test_table.select()) - self.assertEqual(rows.fetchall(), [(2, 3)]) + self.assertEqual([(2, 3)], rows.fetchall()) - transaction_resource.finishedWith(engine) + engine._dispose() + schema_resource.finishedWith(schema) class MySQLRetainSchemaTest(RetainSchemaTest): @@ -153,3 +220,45 @@ class PostgresqlRetainSchemaTest(RetainSchemaTest): DRIVER = "postgresql" + + +class AdHocURLTest(oslo_test_base.BaseTestCase): + def test_sqlite_setup_teardown(self): + + fixture = test_fixtures.AdHocDbFixture("sqlite:///foo.db") + + fixture.setUp() + + self.assertEqual( + str(enginefacade._context_manager._factory._writer_engine.url), + "sqlite:///foo.db" + ) + + self.assertTrue(os.path.exists("foo.db")) + fixture.cleanUp() + + self.assertFalse(os.path.exists("foo.db")) + + def test_mysql_setup_teardown(self): + try: + mysql_backend = provision.Backend.backend_for_database_type( + "mysql") + except exception.BackendNotAvailable: + self.skip("mysql backend not available") + + mysql_backend.create_named_database("adhoc_test") + self.addCleanup( + mysql_backend.drop_named_database, "adhoc_test" + ) + url = str(mysql_backend.provisioned_database_url("adhoc_test")) + + fixture = test_fixtures.AdHocDbFixture(url) + + fixture.setUp() + + self.assertEqual( + str(enginefacade._context_manager._factory._writer_engine.url), + url + ) + + fixture.cleanUp() diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_sqlalchemy.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_sqlalchemy.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_sqlalchemy.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_sqlalchemy.py 2017-01-18 14:09:32.000000000 +0000 @@ -18,6 +18,7 @@ """Unit tests for SQLAlchemy specific code.""" import logging +import os import fixtures import mock @@ -32,10 +33,11 @@ from oslo_db import exception from oslo_db import options as db_options +from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import engines from oslo_db.sqlalchemy import models from oslo_db.sqlalchemy import session -from oslo_db.sqlalchemy import test_base +from oslo_db.tests.sqlalchemy import base as test_base BASE = declarative_base() @@ -64,8 +66,8 @@ self.addCleanup(test_table.drop) def _test_regexp_filter(self, regexp, expected): - _session = self.sessionmaker() - with _session.begin(): + with enginefacade.writer.using(test_base.context): + _session = test_base.context.session for i in ['10', '20', u'♥']: tbl = RegexpTable() tbl.update({'bar': i}) @@ -73,7 +75,7 @@ regexp_op = RegexpTable.bar.op('REGEXP')(regexp) result = _session.query(RegexpTable).filter(regexp_op).all() - self.assertEqual([r.bar for r in result], expected) + self.assertEqual(expected, [r.bar for r in result]) def test_regexp_filter(self): self._test_regexp_filter('10', ['10']) @@ -218,7 +220,7 @@ "SHOW VARIABLES LIKE 'sql_mode'" ).first()[1] - self.assertTrue("TRADITIONAL" in sql_mode) + self.assertIn("TRADITIONAL", sql_mode) class MySQLModeTestCase(test_base.MySQLOpportunisticTestCase): @@ -348,6 +350,9 @@ sqlite_synchronous=mock.ANY, pool_timeout=mock.ANY, thread_checkin=mock.ANY, + json_serializer=None, + json_deserializer=None, + logging_name=mock.ANY, ) get_maker.assert_called_once_with(engine=create_engine(), autocommit=False, @@ -399,15 +404,15 @@ def test_sqlite_fk_listener(self): engine = self._fixture(sqlite_fk=True) self.assertEqual( - engine.scalar("pragma foreign_keys"), - 1 + 1, + engine.scalar("pragma foreign_keys") ) engine = self._fixture(sqlite_fk=False) self.assertEqual( - engine.scalar("pragma foreign_keys"), - 0 + 0, + engine.scalar("pragma foreign_keys") ) def test_sqlite_synchronous_listener(self): @@ -416,15 +421,15 @@ # "The default setting is synchronous=FULL." (e.g. 2) # http://www.sqlite.org/pragma.html#pragma_synchronous self.assertEqual( - engine.scalar("pragma synchronous"), - 2 + 2, + engine.scalar("pragma synchronous") ) engine = self._fixture(sqlite_synchronous=False) self.assertEqual( - engine.scalar("pragma synchronous"), - 0 + 0, + engine.scalar("pragma synchronous") ) @@ -435,12 +440,12 @@ def _assert_sql_mode(self, engine, sql_mode_present, sql_mode_non_present): mode = engine.execute("SHOW VARIABLES LIKE 'sql_mode'").fetchone()[1] - self.assertTrue( - sql_mode_present in mode + self.assertIn( + sql_mode_present, mode ) if sql_mode_non_present: - self.assertTrue( - sql_mode_non_present not in mode + self.assertNotIn( + sql_mode_non_present, mode ) def test_set_mode_traditional(self): @@ -561,8 +566,8 @@ engines._init_connection_args( url.make_url("mysql+pymysql://u:p@host/test"), self.args, max_pool_size=10, max_overflow=10) - self.assertEqual(self.args['pool_size'], 10) - self.assertEqual(self.args['max_overflow'], 10) + self.assertEqual(10, self.args['pool_size']) + self.assertEqual(10, self.args['max_overflow']) def test_sqlite_memory_pool_args(self): for _url in ("sqlite://", "sqlite:///:memory:"): @@ -571,16 +576,16 @@ max_pool_size=10, max_overflow=10) # queuepool arguments are not peresnet - self.assertTrue( - 'pool_size' not in self.args) - self.assertTrue( - 'max_overflow' not in self.args) + self.assertNotIn( + 'pool_size', self.args) + self.assertNotIn( + 'max_overflow', self.args) - self.assertEqual(self.args['connect_args']['check_same_thread'], - False) + self.assertEqual(False, + self.args['connect_args']['check_same_thread']) # due to memory connection - self.assertTrue('poolclass' in self.args) + self.assertIn('poolclass', self.args) def test_sqlite_file_pool_args(self): engines._init_connection_args( @@ -588,23 +593,23 @@ max_pool_size=10, max_overflow=10) # queuepool arguments are not peresnet - self.assertTrue('pool_size' not in self.args) - self.assertTrue( - 'max_overflow' not in self.args) + self.assertNotIn('pool_size', self.args) + self.assertNotIn( + 'max_overflow', self.args) self.assertFalse(self.args['connect_args']) # NullPool is the default for file based connections, # no need to specify this - self.assertTrue('poolclass' not in self.args) + self.assertNotIn('poolclass', self.args) def _test_mysql_connect_args_default(self, connect_args): if six.PY3: - self.assertEqual(connect_args, - {'charset': 'utf8', 'use_unicode': 1}) + self.assertEqual({'charset': 'utf8', 'use_unicode': 1}, + connect_args) else: - self.assertEqual(connect_args, - {'charset': 'utf8', 'use_unicode': 0}) + self.assertEqual({'charset': 'utf8', 'use_unicode': 0}, + connect_args) def test_mysql_connect_args_default(self): engines._init_connection_args( @@ -619,8 +624,7 @@ def test_mysql_pymysql_connect_args_default(self): engines._init_connection_args( url.make_url("mysql+pymysql://u:p@host/test"), self.args) - self.assertEqual(self.args['connect_args'], - {'charset': 'utf8'}) + self.assertEqual({'charset': 'utf8'}, self.args['connect_args']) def test_mysql_mysqldb_connect_args_default(self): engines._init_connection_args( @@ -630,14 +634,14 @@ def test_postgresql_connect_args_default(self): engines._init_connection_args( url.make_url("postgresql://u:p@host/test"), self.args) - self.assertEqual(self.args['client_encoding'], 'utf8') + self.assertEqual('utf8', self.args['client_encoding']) self.assertFalse(self.args['connect_args']) def test_mysqlconnector_raise_on_warnings_default(self): engines._init_connection_args( url.make_url("mysql+mysqlconnector://u:p@host/test"), self.args) - self.assertEqual(self.args['connect_args']['raise_on_warnings'], False) + self.assertEqual(False, self.args['connect_args']['raise_on_warnings']) def test_mysqlconnector_raise_on_warnings_override(self): engines._init_connection_args( @@ -647,7 +651,7 @@ self.args ) - self.assertFalse('raise_on_warnings' in self.args['connect_args']) + self.assertNotIn('raise_on_warnings', self.args['connect_args']) def test_thread_checkin(self): with mock.patch("sqlalchemy.event.listens_for"): @@ -717,4 +721,5 @@ call = mock_exec.mock_calls[0] # we're the caller, see that we're in there - self.assertIn("tests/sqlalchemy/test_sqlalchemy.py", call[1][1]) + caller = os.path.join("tests", "sqlalchemy", "test_sqlalchemy.py") + self.assertIn(caller, call[1][1]) diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_types.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_types.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_types.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_types.py 2017-01-18 14:09:32.000000000 +0000 @@ -13,12 +13,13 @@ """Tests for JSON SQLAlchemy types.""" from sqlalchemy import Column, Integer +from sqlalchemy.dialects import mysql from sqlalchemy.ext.declarative import declarative_base from oslo_db import exception as db_exc from oslo_db.sqlalchemy import models -from oslo_db.sqlalchemy import test_base from oslo_db.sqlalchemy import types +from oslo_db.tests.sqlalchemy import base as test_base BASE = declarative_base() @@ -84,3 +85,27 @@ JsonTable(id=i, json=test).save(self.session) obj = self.session.query(JsonTable).filter_by(id=i).one() self.assertEqual(test, obj.json) + + def test_mysql_variants(self): + self.assertEqual( + "LONGTEXT", + str( + types.JsonEncodedDict(mysql_as_long=True).compile( + dialect=mysql.dialect()) + ) + ) + + self.assertEqual( + "MEDIUMTEXT", + str( + types.JsonEncodedDict(mysql_as_medium=True).compile( + dialect=mysql.dialect()) + ) + ) + + self.assertRaises( + TypeError, + lambda: types.JsonEncodedDict( + mysql_as_long=True, + mysql_as_medium=True) + ) diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_update_match.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_update_match.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_update_match.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_update_match.py 2017-01-18 14:09:32.000000000 +0000 @@ -17,8 +17,8 @@ from sqlalchemy import sql from sqlalchemy import types as sqltypes -from oslo_db.sqlalchemy import test_base from oslo_db.sqlalchemy import update_match +from oslo_db.tests.sqlalchemy import base as test_base Base = declarative.declarative_base() diff -Nru python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_utils.py python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_utils.py --- python-oslo.db-4.7.0/oslo_db/tests/sqlalchemy/test_utils.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/sqlalchemy/test_utils.py 2017-01-18 14:09:32.000000000 +0000 @@ -13,10 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - import fixtures import mock +from oslo_utils import uuidutils from oslotest import base as test_base from oslotest import moxstubout import six @@ -24,23 +23,26 @@ import sqlalchemy from sqlalchemy.dialects import mysql from sqlalchemy import Boolean, Index, Integer, DateTime, String, SmallInteger +from sqlalchemy import CheckConstraint from sqlalchemy import MetaData, Table, Column, ForeignKey from sqlalchemy.engine import reflection from sqlalchemy.engine import url as sa_url from sqlalchemy.exc import OperationalError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import mapper from sqlalchemy.orm import Session from sqlalchemy.sql import select from sqlalchemy.types import UserDefinedType, NullType +from sqlalchemy.dialects.postgresql import psycopg2 from oslo_db import exception from oslo_db.sqlalchemy.compat import utils as compat_utils from oslo_db.sqlalchemy import models from oslo_db.sqlalchemy import provision from oslo_db.sqlalchemy import session -from oslo_db.sqlalchemy import test_base as db_test_base from oslo_db.sqlalchemy import utils +from oslo_db.tests.sqlalchemy import base as db_test_base from oslo_db.tests import utils as test_utils @@ -74,6 +76,7 @@ user_id = Column(String(50), primary_key=True) project_id = Column(String(50)) snapshot_id = Column(String(50)) + updated_at = Column(DateTime, nullable=True) # mox is comparing in some awkward way that # in this case requires the same identity of object @@ -91,6 +94,52 @@ pass +class FakeTableJoinedInh(FakeTable): + __tablename__ = 'fake_table_inh' + + id = Column(String(50), ForeignKey('fake_table.user_id')) + + +class FakeTableSingleInh(FakeTable): + __mapper_args__ = {'polymorphic_identity': 'foo'} + + +class FakeTableWithMultipleKeys(Base): + __tablename__ = 'fake_table_multiple_keys' + + key1 = Column(String(50), primary_key=True) + key2 = Column(String(50), primary_key=True) + key3 = Column(String(50)) + + +class FakeTableWithIndexes(Base): + __tablename__ = 'fake_table_unique_index' + + id = Column(String(50), primary_key=True) + key1 = Column(String(50)) + key2 = Column(String(50)) + key3 = Column(String(50)) + + __table_args__ = ( + Index('idx_unique', 'key1', 'key2', unique=True), + Index('idx_unique', 'key1', 'key3', unique=False), + ) + + +class FakeTableClassicalyMapped(object): + pass + + +fake_table = Table( + 'fake_table_classically_mapped', + Base.metadata, + Column('id', Integer, primary_key=True), + Column('key', String(50)) +) + +mapper(FakeTableClassicalyMapped, fake_table) + + class FakeModel(object): def __init__(self, values): self.values = values @@ -122,7 +171,8 @@ self.mox.StubOutWithMock(sqlalchemy, 'desc') self.marker = FakeTable(user_id='user', project_id='p', - snapshot_id='s') + snapshot_id='s', + updated_at=None) self.model = FakeTable def test_paginate_query_no_pagination_no_sort_dirs(self): @@ -252,6 +302,35 @@ marker=self.marker, sort_dirs=['asc-nullslast', 'desc-nullsfirst']) + def test_paginate_query_marker_null(self): + self.mox.StubOutWithMock(self.model.user_id, 'isnot') + self.model.user_id.isnot(None).AndReturn('asc_null_1') + sqlalchemy.desc('asc_null_1').AndReturn('asc_null_2') + self.query.order_by('asc_null_2').AndReturn(self.query) + + sqlalchemy.asc(self.model.user_id).AndReturn('asc_1') + self.query.order_by('asc_1').AndReturn(self.query) + + self.mox.StubOutWithMock(self.model.updated_at, 'is_') + self.model.updated_at.is_(None).AndReturn('desc_null_1') + sqlalchemy.desc('desc_null_1').AndReturn('desc_null_2') + self.query.order_by('desc_null_2').AndReturn(self.query) + + sqlalchemy.desc(self.model.updated_at).AndReturn('desc_1') + self.query.order_by('desc_1').AndReturn(self.query) + + self.mox.StubOutWithMock(sqlalchemy.sql, 'and_') + sqlalchemy.sql.and_(mock.ANY).AndReturn('some_crit') + self.mox.StubOutWithMock(sqlalchemy.sql, 'or_') + sqlalchemy.sql.or_('some_crit').AndReturn('some_f') + self.query.filter('some_f').AndReturn(self.query) + self.query.limit(5).AndReturn(self.query) + self.mox.ReplayAll() + utils.paginate_query(self.query, self.model, 5, + ['user_id', 'updated_at'], + marker=self.marker, + sort_dirs=['asc-nullslast', 'desc-nullsfirst']) + def test_paginate_query_value_error(self): sqlalchemy.asc(self.model.user_id).AndReturn('asc_1') self.query.order_by('asc_1').AndReturn(self.query) @@ -274,6 +353,101 @@ sort_dirs=['asc', 'desc']) +class Test_UnstableSortingOrder(test_base.BaseTestCase): + def test_multiple_primary_keys_stable(self): + self.assertTrue( + utils._stable_sorting_order( + FakeTableWithMultipleKeys, ['key1', 'key2'])) + + def test_classically_mapped_primary_keys_stable(self): + self.assertTrue( + utils._stable_sorting_order(FakeTableClassicalyMapped, ['id'])) + + def test_multiple_primary_keys_unstable(self): + self.assertFalse( + utils._stable_sorting_order( + FakeTableWithMultipleKeys, ['key1', 'key3'])) + + def test_joined_inh_stable(self): + self.assertTrue( + utils._stable_sorting_order(FakeTableJoinedInh, ['user_id']) + ) + + def test_single_inh_stable(self): + self.assertTrue( + utils._stable_sorting_order(FakeTableSingleInh, ['user_id']) + ) + + def test_unknown_primary_keys_stable(self): + self.assertIsNone( + utils._stable_sorting_order(object, ['key1', 'key2'])) + + def test_unique_index_stable(self): + self.assertTrue( + utils._stable_sorting_order( + FakeTableWithIndexes, ['key1', 'key2'])) + + def test_unique_index_unstable(self): + self.assertFalse( + utils._stable_sorting_order( + FakeTableWithIndexes, ['key1', 'key3'])) + + +class TestGetUniqueKeys(test_base.BaseTestCase): + def test_multiple_primary_keys(self): + self.assertEqual( + [{'key1', 'key2'}], + utils.get_unique_keys(FakeTableWithMultipleKeys)) + + def test_unique_index(self): + self.assertEqual( + [{'id'}, {'key1', 'key2'}], + utils.get_unique_keys(FakeTableWithIndexes)) + + def test_unknown_primary_keys(self): + self.assertIsNone(utils.get_unique_keys(object)) + + def test_cache(self): + + class CacheTable(object): + info = {} + constraints_called = 0 + indexes_called = 0 + + @property + def constraints(self): + self.constraints_called += 1 + return [] + + @property + def indexes(self): + self.indexes_called += 1 + return [] + + class CacheModel(object): + pass + + table = CacheTable() + mapper_mock = mock.Mock(mapped_table=table, local_table=table) + mapper_mock.base_mapper = mapper_mock + mock_inspect = mock.Mock( + return_value=mapper_mock) + model = CacheModel() + self.assertNotIn('oslodb_unique_keys', CacheTable.info) + with mock.patch("oslo_db.sqlalchemy.utils.inspect", mock_inspect): + utils.get_unique_keys(model) + + self.assertIn('oslodb_unique_keys', CacheTable.info) + self.assertEqual(1, table.constraints_called) + self.assertEqual(1, table.indexes_called) + + for i in range(10): + utils.get_unique_keys(model) + + self.assertEqual(1, table.constraints_called) + self.assertEqual(1, table.indexes_called) + + class TestPaginateQueryActualSQL(test_base.BaseTestCase): def test_paginate_on_hybrid_assert_stmt(self): @@ -291,8 +465,8 @@ ) self.assertEqual( - str(q.statement.compile()), - str(expected_core_sql.compile()) + str(expected_core_sql.compile()), + str(q.statement.compile()) ) @@ -355,9 +529,9 @@ real_ids = [row[0] for row in self.engine.execute(select([test_table.c.id])).fetchall()] - self.assertEqual(len(real_ids), len(expected_ids)) + self.assertEqual(len(expected_ids), len(real_ids)) for id_ in expected_ids: - self.assertTrue(id_ in real_ids) + self.assertIn(id_, real_ids) def test_drop_dup_entries_in_file_conn(self): table_name = "__test_tmp_table__" @@ -395,19 +569,19 @@ rows_select = base_select.where(table.c.deleted != table.c.id) row_ids = [row['id'] for row in self.engine.execute(rows_select).fetchall()] - self.assertEqual(len(row_ids), len(expected_values)) + self.assertEqual(len(expected_values), len(row_ids)) for value in expected_values: - self.assertTrue(value['id'] in row_ids) + self.assertIn(value['id'], row_ids) deleted_rows_select = base_select.where( table.c.deleted == table.c.id) deleted_rows_ids = [row['id'] for row in self.engine.execute( deleted_rows_select).fetchall()] - self.assertEqual(len(deleted_rows_ids), - len(values) - len(row_ids)) + self.assertEqual(len(values) - len(row_ids), + len(deleted_rows_ids)) for value in soft_deleted_values: - self.assertTrue(value['id'] in deleted_rows_ids) + self.assertIn(value['id'], deleted_rows_ids) def test_change_deleted_column_type_does_not_drop_index(self): table_name = 'abc' @@ -433,12 +607,12 @@ insp = reflection.Inspector.from_engine(self.engine) real_indexes = insp.get_indexes(table_name) - self.assertEqual(len(real_indexes), 3) + self.assertEqual(3, len(real_indexes)) for index in real_indexes: name = index['name'] self.assertIn(name, indexes) - self.assertEqual(set(index['column_names']), - set(indexes[name])) + self.assertEqual(set(indexes[name]), + set(index['column_names'])) def test_change_deleted_column_type_to_id_type_integer(self): table_name = 'abc' @@ -449,7 +623,7 @@ utils.change_deleted_column_type_to_id_type(self.engine, table_name) table = utils.get_table(self.engine, table_name) - self.assertTrue(isinstance(table.c.deleted.type, Integer)) + self.assertIsInstance(table.c.deleted.type, Integer) def test_change_deleted_column_type_to_id_type_string(self): table_name = 'abc' @@ -460,7 +634,7 @@ utils.change_deleted_column_type_to_id_type(self.engine, table_name) table = utils.get_table(self.engine, table_name) - self.assertTrue(isinstance(table.c.deleted.type, String)) + self.assertIsInstance(table.c.deleted.type, String) @db_test_base.backend_specific('sqlite') def test_change_deleted_column_type_to_id_type_custom(self): @@ -482,12 +656,8 @@ foo=fooColumn) table = utils.get_table(self.engine, table_name) - # NOTE(boris-42): There is no way to check has foo type CustomType. - # but sqlalchemy will set it to NullType. This has - # been fixed upstream in recent SA versions - if SA_VERSION < (0, 9, 0): - self.assertTrue(isinstance(table.c.foo.type, NullType)) - self.assertTrue(isinstance(table.c.deleted.type, Integer)) + + self.assertIsInstance(table.c.deleted.type, Integer) def test_change_deleted_column_type_to_boolean(self): expected_types = {'mysql': mysql.TINYINT, @@ -537,12 +707,6 @@ Column('deleted', Integer)) table.create() - # reflection of custom types has been fixed upstream - if SA_VERSION < (0, 9, 0): - self.assertRaises(exception.ColumnError, - utils.change_deleted_column_type_to_boolean, - self.engine, table_name) - fooColumn = Column('foo', CustomType()) utils.change_deleted_column_type_to_boolean(self.engine, table_name, foo=fooColumn) @@ -552,8 +716,25 @@ # but sqlalchemy will set it to NullType. This has # been fixed upstream in recent SA versions if SA_VERSION < (0, 9, 0): - self.assertTrue(isinstance(table.c.foo.type, NullType)) - self.assertTrue(isinstance(table.c.deleted.type, Boolean)) + self.assertIsInstance(table.c.foo.type, NullType) + self.assertIsInstance(table.c.deleted.type, Boolean) + + def test_detect_boolean_deleted_constraint_detection(self): + table_name = 'abc' + table = Table(table_name, self.meta, + Column('id', Integer, primary_key=True), + Column('deleted', Boolean)) + ck = [ + const for const in table.constraints if + isinstance(const, CheckConstraint)][0] + + self.assertTrue(utils._is_deleted_column_constraint(ck)) + + self.assertFalse( + utils._is_deleted_column_constraint( + CheckConstraint("deleted > 5") + ) + ) @db_test_base.backend_specific('sqlite') def test_change_deleted_column_type_sqlite_drops_check_constraint(self): @@ -575,7 +756,7 @@ select_table_name = "__test_select_from_table__" uuidstrs = [] for unused in range(10): - uuidstrs.append(uuid.uuid4().hex) + uuidstrs.append(uuidutils.generate_uuid(dashed=False)) insert_table = Table( insert_table_name, self.meta, Column('id', Integer, primary_key=True, @@ -602,20 +783,20 @@ query_insert) result_insert = self.conn.execute(insert_statement) # Verify we insert 4 rows - self.assertEqual(result_insert.rowcount, 4) + self.assertEqual(4, result_insert.rowcount) query_all = select([insert_table]).where( insert_table.c.uuid.in_(uuidstrs)) rows = self.conn.execute(query_all).fetchall() # Verify we really have 4 rows in insert_table - self.assertEqual(len(rows), 4) + self.assertEqual(4, len(rows)) def test_insert_from_select_with_specified_columns(self): insert_table_name = "__test_insert_to_table__" select_table_name = "__test_select_from_table__" uuidstrs = [] for unused in range(10): - uuidstrs.append(uuid.uuid4().hex) + uuidstrs.append(uuidutils.generate_uuid(dashed=False)) insert_table = Table( insert_table_name, self.meta, Column('id', Integer, primary_key=True, @@ -642,20 +823,20 @@ query_insert, ['id', 'uuid']) result_insert = self.conn.execute(insert_statement) # Verify we insert 4 rows - self.assertEqual(result_insert.rowcount, 4) + self.assertEqual(4, result_insert.rowcount) query_all = select([insert_table]).where( insert_table.c.uuid.in_(uuidstrs)) rows = self.conn.execute(query_all).fetchall() # Verify we really have 4 rows in insert_table - self.assertEqual(len(rows), 4) + self.assertEqual(4, len(rows)) def test_insert_from_select_with_specified_columns_negative(self): insert_table_name = "__test_insert_to_table__" select_table_name = "__test_select_from_table__" uuidstrs = [] for unused in range(10): - uuidstrs.append(uuid.uuid4().hex) + uuidstrs.append(uuidutils.generate_uuid(dashed=False)) insert_table = Table( insert_table_name, self.meta, Column('id', Integer, primary_key=True, @@ -710,14 +891,30 @@ self.connect_string = 'postgresql://dude:pass@localhost/test' + # NOTE(rpodolyaka): mock the dialect parts, so that we don't depend + # on psycopg2 (or any other DBAPI implementation) in these tests + + @classmethod + def fake_dbapi(cls): + return mock.MagicMock() + patch_dbapi = mock.patch.object(psycopg2.PGDialect_psycopg2, 'dbapi', + new=fake_dbapi) + patch_dbapi.start() + self.addCleanup(patch_dbapi.stop) + + patch_onconnect = mock.patch.object(psycopg2.PGDialect_psycopg2, + 'on_connect') + patch_onconnect.start() + self.addCleanup(patch_onconnect.stop) + def test_connect_string(self): connect_string = utils.get_connect_string(**self.full_credentials) - self.assertEqual(connect_string, self.connect_string) + self.assertEqual(self.connect_string, connect_string) def test_connect_string_sqlite(self): sqlite_credentials = {'backend': 'sqlite', 'database': 'test.db'} connect_string = utils.get_connect_string(**sqlite_credentials) - self.assertEqual(connect_string, 'sqlite:///test.db') + self.assertEqual('sqlite:///test.db', connect_string) def test_is_backend_avail(self): self.mox.StubOutWithMock(sqlalchemy.engine.base.Engine, 'connect') @@ -760,7 +957,9 @@ exception.BackendNotAvailable, provision.Backend._ensure_backend_available, self.connect_string ) - self.assertEqual("Could not connect", str(exc)) + self.assertEqual( + "Backend 'postgresql' is unavailable: " + "Could not connect", str(exc)) self.assertEqual( "The postgresql backend is unavailable: %s" % err, log.output.strip()) @@ -777,20 +976,22 @@ exception.BackendNotAvailable, provision.Backend._ensure_backend_available, self.connect_string ) - self.assertEqual("No DBAPI installed", str(exc)) + self.assertEqual( + "Backend 'postgresql' is unavailable: " + "No DBAPI installed", str(exc)) self.assertEqual( "The postgresql backend is unavailable: Can't import " "DBAPI module foobar", log.output.strip()) def test_get_db_connection_info(self): conn_pieces = parse.urlparse(self.connect_string) - self.assertEqual(utils.get_db_connection_info(conn_pieces), - ('dude', 'pass', 'test', 'localhost')) + self.assertEqual(('dude', 'pass', 'test', 'localhost'), + utils.get_db_connection_info(conn_pieces)) def test_connect_string_host(self): self.full_credentials['host'] = 'myhost' connect_string = utils.get_connect_string(**self.full_credentials) - self.assertEqual(connect_string, 'postgresql://dude:pass@myhost/test') + self.assertEqual('postgresql://dude:pass@myhost/test', connect_string) class MyModelSoftDeletedProjectId(declarative_base(), models.ModelBase, @@ -833,8 +1034,8 @@ MyModelSoftDeleted, session=self.session, deleted=False) deleted_filter = mock_query.filter.call_args[0][0] - self.assertEqual(str(deleted_filter), - 'soft_deleted_test_model.deleted = :deleted_1') + self.assertEqual('soft_deleted_test_model.deleted = :deleted_1', + str(deleted_filter)) self.assertEqual(deleted_filter.right.value, MyModelSoftDeleted.__mapper__.c.deleted.default.arg) @@ -851,7 +1052,7 @@ @mock.patch.object(utils, "_read_deleted_filter") def test_no_deleted_value(self, _read_deleted_filter): utils.model_query(MyModelSoftDeleted, session=self.session) - self.assertEqual(_read_deleted_filter.call_count, 0) + self.assertEqual(0, _read_deleted_filter.call_count) def test_project_filter(self): project_id = 10 @@ -862,9 +1063,9 @@ deleted_filter = mock_query.filter.call_args[0][0] self.assertEqual( - str(deleted_filter), - 'soft_deleted_project_id_test_model.project_id = :project_id_1') - self.assertEqual(deleted_filter.right.value, project_id) + 'soft_deleted_project_id_test_model.project_id = :project_id_1', + str(deleted_filter)) + self.assertEqual(project_id, deleted_filter.right.value) def test_project_filter_wrong_model(self): self.assertRaises(ValueError, utils.model_query, @@ -877,9 +1078,9 @@ session=self.session, project_id=(10, None)) self.assertEqual( - str(mock_query.filter.call_args[0][0]), 'soft_deleted_project_id_test_model.project_id' - ' IN (:project_id_1, NULL)' + ' IN (:project_id_1, NULL)', + str(mock_query.filter.call_args[0][0]) ) def test_model_query_common(self): @@ -900,6 +1101,14 @@ self.test_table.create() self.addCleanup(meta.drop_all) + def test_get_indexes(self): + Index('index_a', self.test_table.c.a).create(self.engine) + Index('index_b', self.test_table.c.b).create(self.engine) + indexes = utils.get_indexes(self.engine, "test_table") + indexes = [(index['name'], index['column_names']) for index in indexes] + self.assertIn(('index_a', ['a']), indexes) + self.assertIn(('index_b', ['b']), indexes) + def test_index_exists(self): self.assertFalse(utils.index_exists(self.engine, 'test_table', 'new_index')) @@ -907,6 +1116,13 @@ self.assertTrue(utils.index_exists(self.engine, 'test_table', 'new_index')) + def test_index_exists_on_columns(self): + columns = [self.test_table.c.a, self.test_table.c.b] + Index('new_index', *columns).create(self.engine) + self.assertTrue(utils.index_exists_on_columns(self.engine, + 'test_table', + ('a', 'b'))) + def test_add_index(self): self.assertFalse(utils.index_exists(self.engine, 'test_table', 'new_index')) @@ -1143,7 +1359,7 @@ callable_fn.mysql_pymysql.return_value = 5 self.assertEqual( - dispatcher("mysql+pymysql://u:p@h/t", 3), 5 + 5, dispatcher("mysql+pymysql://u:p@h/t", 3) ) def test_engine(self): diff -Nru python-oslo.db-4.7.0/oslo_db/tests/test_api.py python-oslo.db-4.17.0/oslo_db/tests/test_api.py --- python-oslo.db-4.7.0/oslo_db/tests/test_api.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/test_api.py 2017-01-18 14:09:32.000000000 +0000 @@ -123,7 +123,8 @@ self.dbapi.api_raise_default) self.assertEqual(4, self.test_db_api.error_counter, 'Unexpected retry') - def test_retry_one(self): + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_one(self, p_time_sleep): self.dbapi = api.DBAPI('sqlalchemy', {'sqlalchemy': __name__}, use_db_reconnect=True, @@ -135,12 +136,13 @@ self.assertTrue(func(), 'Single retry did not succeed.') except Exception: self.fail('Single retry raised an un-wrapped error.') - + p_time_sleep.assert_called_with(1) self.assertEqual( 0, self.test_db_api.error_counter, 'Counter not decremented, retry logic probably failed.') - def test_retry_two(self): + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_two(self, p_time_sleep): self.dbapi = api.DBAPI('sqlalchemy', {'sqlalchemy': __name__}, use_db_reconnect=True, @@ -153,12 +155,31 @@ self.assertTrue(func(), 'Multiple retry did not succeed.') except Exception: self.fail('Multiple retry raised an un-wrapped error.') + p_time_sleep.assert_called_with(1) + self.assertEqual( + 0, self.test_db_api.error_counter, + 'Counter not decremented, retry logic probably failed.') + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_float_interval(self, p_time_sleep): + self.dbapi = api.DBAPI('sqlalchemy', + {'sqlalchemy': __name__}, + use_db_reconnect=True, + retry_interval=0.5) + try: + func = self.dbapi.api_raise_enable_retry + self.test_db_api.error_counter = 1 + self.assertTrue(func(), 'Single retry did not succeed.') + except Exception: + self.fail('Single retry raised an un-wrapped error.') + + p_time_sleep.assert_called_with(0.5) self.assertEqual( 0, self.test_db_api.error_counter, 'Counter not decremented, retry logic probably failed.') - def test_retry_until_failure(self): + @mock.patch('oslo_db.api.time.sleep', return_value=None) + def test_retry_until_failure(self, p_time_sleep): self.dbapi = api.DBAPI('sqlalchemy', {'sqlalchemy': __name__}, use_db_reconnect=True, @@ -171,7 +192,7 @@ self.assertRaises( exception.DBError, func, 'Retry of permanent failure did not throw DBError exception.') - + p_time_sleep.assert_called_with(1) self.assertNotEqual( 0, self.test_db_api.error_counter, 'Retry did not stop after sql_max_retries iterations.') @@ -179,16 +200,16 @@ class DBRetryRequestCase(DBAPITestCase): def test_retry_wrapper_succeeds(self): - @api.wrap_db_retry(max_retries=10, retry_on_request=True) + @api.wrap_db_retry(max_retries=10) def some_method(): pass some_method() def test_retry_wrapper_reaches_limit(self): - max_retries = 10 + max_retries = 2 - @api.wrap_db_retry(max_retries=10, retry_on_request=True) + @api.wrap_db_retry(max_retries=max_retries) def some_method(res): res['result'] += 1 raise exception.RetryRequest(ValueError()) @@ -202,7 +223,7 @@ def exception_checker(exc): return isinstance(exc, ValueError) and exc.args[0] < 5 - @api.wrap_db_retry(max_retries=10, retry_on_request=True, + @api.wrap_db_retry(max_retries=10, exception_checker=exception_checker) def some_method(res): res['result'] += 1 diff -Nru python-oslo.db-4.7.0/oslo_db/tests/test_concurrency.py python-oslo.db-4.17.0/oslo_db/tests/test_concurrency.py --- python-oslo.db-4.7.0/oslo_db/tests/test_concurrency.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/oslo_db/tests/test_concurrency.py 2017-01-18 14:09:32.000000000 +0000 @@ -63,13 +63,13 @@ mock_db_api.from_config.assert_called_once_with( conf=self.conf, backend_mapping=FAKE_BACKEND_MAPPING) - self.assertEqual(self.db_api._db_api, fake_db_api) + self.assertEqual(fake_db_api, self.db_api._db_api) self.assertFalse(self.eventlet.tpool.Proxy.called) # get access to other db-api method to be sure that api didn't changed self.db_api.fake_call_2 - self.assertEqual(self.db_api._db_api, fake_db_api) + self.assertEqual(fake_db_api, self.db_api._db_api) self.assertFalse(self.eventlet.tpool.Proxy.called) self.assertEqual(1, mock_db_api.from_config.call_count) @@ -92,7 +92,7 @@ mock_db_api.from_config.assert_called_once_with( conf=self.conf, backend_mapping=FAKE_BACKEND_MAPPING) self.eventlet.tpool.Proxy.assert_called_once_with(fake_db_api) - self.assertEqual(self.db_api._db_api, self.proxy) + self.assertEqual(self.proxy, self.db_api._db_api) @mock.patch('oslo_db.api.DBAPI') def test_db_api_without_installed_eventlet(self, mock_db_api): diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/dependency_links.txt python-oslo.db-4.17.0/oslo.db.egg-info/dependency_links.txt --- python-oslo.db-4.7.0/oslo.db.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/dependency_links.txt 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/entry_points.txt python-oslo.db-4.17.0/oslo.db.egg-info/entry_points.txt --- python-oslo.db-4.7.0/oslo.db.egg-info/entry_points.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/entry_points.txt 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,8 @@ +[oslo.config.opts] +oslo.db = oslo_db.options:list_opts +oslo.db.concurrency = oslo_db.concurrency:list_opts + +[oslo.db.migration] +alembic = oslo_db.sqlalchemy.migration_cli.ext_alembic:AlembicExtension +migrate = oslo_db.sqlalchemy.migration_cli.ext_migrate:MigrateExtension + diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/not-zip-safe python-oslo.db-4.17.0/oslo.db.egg-info/not-zip-safe --- python-oslo.db-4.7.0/oslo.db.egg-info/not-zip-safe 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/not-zip-safe 2017-01-18 14:11:08.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/pbr.json python-oslo.db-4.17.0/oslo.db.egg-info/pbr.json --- python-oslo.db-4.7.0/oslo.db.egg-info/pbr.json 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/pbr.json 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1 @@ +{"git_version": "5095c7c", "is_release": true} \ No newline at end of file diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/PKG-INFO python-oslo.db-4.17.0/oslo.db.egg-info/PKG-INFO --- python-oslo.db-4.7.0/oslo.db.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/PKG-INFO 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,51 @@ +Metadata-Version: 1.1 +Name: oslo.db +Version: 4.17.0 +Summary: Oslo Database library +Home-page: http://docs.openstack.org/developer/oslo.db +Author: OpenStack +Author-email: openstack-dev@lists.openstack.org +License: UNKNOWN +Description: ======================== + Team and repository tags + ======================== + + .. image:: http://governance.openstack.org/badges/oslo.db.svg + :target: http://governance.openstack.org/reference/tags/index.html + + .. Change things from this point on + + =============================================== + oslo.db -- OpenStack Database Pattern Library + =============================================== + + .. image:: https://img.shields.io/pypi/v/oslo.db.svg + :target: https://pypi.python.org/pypi/oslo.db/ + :alt: Latest Version + + .. image:: https://img.shields.io/pypi/dm/oslo.db.svg + :target: https://pypi.python.org/pypi/oslo.db/ + :alt: Downloads + + The oslo db (database) handling library, provides database + connectivity to different database backends and various other helper + utils. + + * Free software: Apache license + * Documentation: http://docs.openstack.org/developer/oslo.db + * Source: http://git.openstack.org/cgit/openstack/oslo.db + * Bugs: http://bugs.launchpad.net/oslo.db + + +Platform: UNKNOWN +Classifier: Environment :: OpenStack +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/requires.txt python-oslo.db-4.17.0/oslo.db.egg-info/requires.txt --- python-oslo.db-4.7.0/oslo.db.egg-info/requires.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/requires.txt 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,43 @@ +pbr>=1.8 +alembic>=0.8.4 +debtcollector>=1.2.0 +oslo.i18n>=2.1.0 +oslo.config!=3.18.0,>=3.14.0 +oslo.context>=2.9.0 +oslo.utils>=3.18.0 +SQLAlchemy<1.1.0,>=1.0.10 +sqlalchemy-migrate>=0.9.6 +stevedore>=1.17.1 +six>=1.9.0 + +[fixtures] +testresources>=0.2.4 # Apache-2.0/BSD +testscenarios>=0.4 # Apache-2.0/BSD + +[mysql] +PyMySQL>=0.7.6 # MIT License + +[mysql-c] +MySQL-python + +[pifpaf] +pifpaf>=0.10.0 # Apache-2.0 + +[postgresql] +psycopg2>=2.5 # LGPL/ZPL + +[test] +hacking<0.11,>=0.10.0 +coverage>=4.0 # Apache-2.0 +doc8 # Apache-2.0 +eventlet!=0.18.3,>=0.18.2 # MIT +fixtures>=3.0.0 # Apache-2.0/BSD +mock>=2.0 # BSD +python-subunit>=0.0.18 # Apache-2.0/BSD +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD +oslosphinx>=4.7.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 +testrepository>=0.0.18 # Apache-2.0/BSD +testtools>=1.4.0 # MIT +os-testr>=0.8.0 # Apache-2.0 +reno>=1.8.0 # Apache-2.0 diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/SOURCES.txt python-oslo.db-4.17.0/oslo.db.egg-info/SOURCES.txt --- python-oslo.db-4.7.0/oslo.db.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/SOURCES.txt 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,111 @@ +.coveragerc +.mailmap +.testr.conf +AUTHORS +CONTRIBUTING.rst +ChangeLog +HACKING.rst +LICENSE +README.rst +babel.cfg +requirements.txt +setup.cfg +setup.py +tox.ini +doc/source/conf.py +doc/source/contributing.rst +doc/source/history.rst +doc/source/index.rst +doc/source/installation.rst +doc/source/opts.rst +doc/source/usage.rst +oslo.db.egg-info/PKG-INFO +oslo.db.egg-info/SOURCES.txt +oslo.db.egg-info/dependency_links.txt +oslo.db.egg-info/entry_points.txt +oslo.db.egg-info/not-zip-safe +oslo.db.egg-info/pbr.json +oslo.db.egg-info/requires.txt +oslo.db.egg-info/top_level.txt +oslo_db/__init__.py +oslo_db/_i18n.py +oslo_db/api.py +oslo_db/concurrency.py +oslo_db/exception.py +oslo_db/options.py +oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-error.po +oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-info.po +oslo_db/locale/en_GB/LC_MESSAGES/oslo_db-log-warning.po +oslo_db/locale/en_GB/LC_MESSAGES/oslo_db.po +oslo_db/locale/es/LC_MESSAGES/oslo_db-log-error.po +oslo_db/locale/es/LC_MESSAGES/oslo_db-log-info.po +oslo_db/locale/es/LC_MESSAGES/oslo_db-log-warning.po +oslo_db/locale/es/LC_MESSAGES/oslo_db.po +oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-error.po +oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-info.po +oslo_db/locale/fr/LC_MESSAGES/oslo_db-log-warning.po +oslo_db/locale/fr/LC_MESSAGES/oslo_db.po +oslo_db/sqlalchemy/__init__.py +oslo_db/sqlalchemy/enginefacade.py +oslo_db/sqlalchemy/engines.py +oslo_db/sqlalchemy/exc_filters.py +oslo_db/sqlalchemy/migration.py +oslo_db/sqlalchemy/models.py +oslo_db/sqlalchemy/orm.py +oslo_db/sqlalchemy/provision.py +oslo_db/sqlalchemy/session.py +oslo_db/sqlalchemy/test_base.py +oslo_db/sqlalchemy/test_fixtures.py +oslo_db/sqlalchemy/test_migrations.py +oslo_db/sqlalchemy/types.py +oslo_db/sqlalchemy/update_match.py +oslo_db/sqlalchemy/utils.py +oslo_db/sqlalchemy/compat/__init__.py +oslo_db/sqlalchemy/compat/utils.py +oslo_db/sqlalchemy/migration_cli/README.rst +oslo_db/sqlalchemy/migration_cli/__init__.py +oslo_db/sqlalchemy/migration_cli/ext_alembic.py +oslo_db/sqlalchemy/migration_cli/ext_base.py +oslo_db/sqlalchemy/migration_cli/ext_migrate.py +oslo_db/sqlalchemy/migration_cli/manager.py +oslo_db/tests/__init__.py +oslo_db/tests/base.py +oslo_db/tests/test_api.py +oslo_db/tests/test_concurrency.py +oslo_db/tests/utils.py +oslo_db/tests/sqlalchemy/__init__.py +oslo_db/tests/sqlalchemy/base.py +oslo_db/tests/sqlalchemy/test_async_eventlet.py +oslo_db/tests/sqlalchemy/test_enginefacade.py +oslo_db/tests/sqlalchemy/test_exc_filters.py +oslo_db/tests/sqlalchemy/test_fixtures.py +oslo_db/tests/sqlalchemy/test_migrate_cli.py +oslo_db/tests/sqlalchemy/test_migration_common.py +oslo_db/tests/sqlalchemy/test_migrations.py +oslo_db/tests/sqlalchemy/test_models.py +oslo_db/tests/sqlalchemy/test_options.py +oslo_db/tests/sqlalchemy/test_provision.py +oslo_db/tests/sqlalchemy/test_sqlalchemy.py +oslo_db/tests/sqlalchemy/test_types.py +oslo_db/tests/sqlalchemy/test_update_match.py +oslo_db/tests/sqlalchemy/test_utils.py +releasenotes/notes/add-reno-e5c2f63e73c25959.yaml +releasenotes/notes/connection_debug_min_max-bf6d53d49be7ca52.yaml +releasenotes/notes/deprecate-insert-from-select-ea831381ebd7e7cf.yaml +releasenotes/notes/deprecate_config_sqlite_db-bd41d49343049319.yaml +releasenotes/notes/enginefacade_decorators-4660862fe22d2669.yaml +releasenotes/notes/increase-default-max-overflow-0af787268807f926.yaml +releasenotes/notes/new-db-fixtures-58223e3926122413.yaml +releasenotes/notes/wrap_db_retry-34c7ff2d82afa3f5.yaml +releasenotes/source/conf.py +releasenotes/source/index.rst +releasenotes/source/liberty.rst +releasenotes/source/mitaka.rst +releasenotes/source/newton.rst +releasenotes/source/unreleased.rst +releasenotes/source/_static/.placeholder +releasenotes/source/_templates/.placeholder +releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po +tools/pretty_tox.sh +tools/run-pifpaf-tests.sh +tools/tox_install.sh \ No newline at end of file diff -Nru python-oslo.db-4.7.0/oslo.db.egg-info/top_level.txt python-oslo.db-4.17.0/oslo.db.egg-info/top_level.txt --- python-oslo.db-4.7.0/oslo.db.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/oslo.db.egg-info/top_level.txt 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1 @@ +oslo_db diff -Nru python-oslo.db-4.7.0/PKG-INFO python-oslo.db-4.17.0/PKG-INFO --- python-oslo.db-4.7.0/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/PKG-INFO 2017-01-18 14:12:11.000000000 +0000 @@ -0,0 +1,51 @@ +Metadata-Version: 1.1 +Name: oslo.db +Version: 4.17.0 +Summary: Oslo Database library +Home-page: http://docs.openstack.org/developer/oslo.db +Author: OpenStack +Author-email: openstack-dev@lists.openstack.org +License: UNKNOWN +Description: ======================== + Team and repository tags + ======================== + + .. image:: http://governance.openstack.org/badges/oslo.db.svg + :target: http://governance.openstack.org/reference/tags/index.html + + .. Change things from this point on + + =============================================== + oslo.db -- OpenStack Database Pattern Library + =============================================== + + .. image:: https://img.shields.io/pypi/v/oslo.db.svg + :target: https://pypi.python.org/pypi/oslo.db/ + :alt: Latest Version + + .. image:: https://img.shields.io/pypi/dm/oslo.db.svg + :target: https://pypi.python.org/pypi/oslo.db/ + :alt: Downloads + + The oslo db (database) handling library, provides database + connectivity to different database backends and various other helper + utils. + + * Free software: Apache license + * Documentation: http://docs.openstack.org/developer/oslo.db + * Source: http://git.openstack.org/cgit/openstack/oslo.db + * Bugs: http://bugs.launchpad.net/oslo.db + + +Platform: UNKNOWN +Classifier: Environment :: OpenStack +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 diff -Nru python-oslo.db-4.7.0/README.rst python-oslo.db-4.17.0/README.rst --- python-oslo.db-4.7.0/README.rst 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/README.rst 2017-01-18 14:09:32.000000000 +0000 @@ -1,3 +1,12 @@ +======================== +Team and repository tags +======================== + +.. image:: http://governance.openstack.org/badges/oslo.db.svg + :target: http://governance.openstack.org/reference/tags/index.html + +.. Change things from this point on + =============================================== oslo.db -- OpenStack Database Pattern Library =============================================== diff -Nru python-oslo.db-4.7.0/releasenotes/notes/connection_debug_min_max-bf6d53d49be7ca52.yaml python-oslo.db-4.17.0/releasenotes/notes/connection_debug_min_max-bf6d53d49be7ca52.yaml --- python-oslo.db-4.7.0/releasenotes/notes/connection_debug_min_max-bf6d53d49be7ca52.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/connection_debug_min_max-bf6d53d49be7ca52.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,7 @@ +--- +upgrade: + - The allowed values for the ``connection_debug`` option are now restricted to + the range between 0 and 100 (inclusive). Previously a number lower than 0 + or higher than 100 could be given without error. But now, a + ``ConfigFileValueError`` will be raised when the option value is outside this + range. diff -Nru python-oslo.db-4.7.0/releasenotes/notes/deprecate_config_sqlite_db-bd41d49343049319.yaml python-oslo.db-4.17.0/releasenotes/notes/deprecate_config_sqlite_db-bd41d49343049319.yaml --- python-oslo.db-4.7.0/releasenotes/notes/deprecate_config_sqlite_db-bd41d49343049319.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/deprecate_config_sqlite_db-bd41d49343049319.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,7 @@ +--- +deprecations: + - | + The configuration option ``sqlite_db`` is now deprecated and + will be removed in the future. Please use configuration + option ``connection`` or ``slave_connection`` to connect to the database. + diff -Nru python-oslo.db-4.7.0/releasenotes/notes/deprecate-insert-from-select-ea831381ebd7e7cf.yaml python-oslo.db-4.17.0/releasenotes/notes/deprecate-insert-from-select-ea831381ebd7e7cf.yaml --- python-oslo.db-4.7.0/releasenotes/notes/deprecate-insert-from-select-ea831381ebd7e7cf.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/deprecate-insert-from-select-ea831381ebd7e7cf.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,6 @@ +--- +deprecations: + - class ``InsertFromSelect`` from module ``oslo_db.sqlalchemy.utils`` is + deprecated in favor of ``sqlalchemy.sql.expression.Insert.from_select()`` + method of Insert expression, that is available in SQLAlchemy versions + 1.0.0 and newer diff -Nru python-oslo.db-4.7.0/releasenotes/notes/enginefacade_decorators-4660862fe22d2669.yaml python-oslo.db-4.17.0/releasenotes/notes/enginefacade_decorators-4660862fe22d2669.yaml --- python-oslo.db-4.7.0/releasenotes/notes/enginefacade_decorators-4660862fe22d2669.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/enginefacade_decorators-4660862fe22d2669.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - enginefacade decorators can now be used for class and instance methods, + which implicitly receive the first positional argument. Previously, it + was required that all decorated functions receive a context value as the + first argument. diff -Nru python-oslo.db-4.7.0/releasenotes/notes/new-db-fixtures-58223e3926122413.yaml python-oslo.db-4.17.0/releasenotes/notes/new-db-fixtures-58223e3926122413.yaml --- python-oslo.db-4.7.0/releasenotes/notes/new-db-fixtures-58223e3926122413.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/new-db-fixtures-58223e3926122413.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,5 @@ +--- +deprecations: + - base test classes from ``oslo_db.sqlalchemy.test_base`` are deprecated in + favor of new fixtures introduced in ``oslo_db.sqlalchemy.test_fixtures`` + module diff -Nru python-oslo.db-4.7.0/releasenotes/notes/wrap_db_retry-34c7ff2d82afa3f5.yaml python-oslo.db-4.17.0/releasenotes/notes/wrap_db_retry-34c7ff2d82afa3f5.yaml --- python-oslo.db-4.7.0/releasenotes/notes/wrap_db_retry-34c7ff2d82afa3f5.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/notes/wrap_db_retry-34c7ff2d82afa3f5.yaml 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,6 @@ +--- +fixes: + - Decorator ``oslo_db.api.wrap_db_retry`` now defaults to 10 retries. + Previously the number of attempts was 0, and users had to explicitly + pass ``max_retry_interval`` value greater than 0 to actually enable + retries on errors. diff -Nru python-oslo.db-4.7.0/releasenotes/source/conf.py python-oslo.db-4.17.0/releasenotes/source/conf.py --- python-oslo.db-4.7.0/releasenotes/source/conf.py 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/source/conf.py 2017-01-18 14:09:32.000000000 +0000 @@ -190,7 +190,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'olso.configReleaseNotesdoc' +htmlhelp_basename = 'oslo.configReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- @@ -210,8 +210,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'olso.configReleaseNotes.tex', - u'olso.db Release Notes Documentation', + ('index', 'oslo.configReleaseNotes.tex', + u'oslo.db Release Notes Documentation', u'oslo.db Developers', 'manual'), ] @@ -241,7 +241,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'olso.configreleasenotes', + ('index', 'oslo.configreleasenotes', u'oslo.db Release Notes Documentation', [u'oslo.db Developers'], 1) ] @@ -258,7 +258,7 @@ texinfo_documents = [ ('index', 'oslo.dbReleaseNotes', u'oslo.db Release Notes Documentation', - u'oslo.db Developers', 'olso.configReleaseNotes', + u'oslo.db Developers', 'oslo.configReleaseNotes', 'An OpenStack library for parsing configuration options from the command' ' line and configuration files.', 'Miscellaneous'), @@ -275,3 +275,6 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff -Nru python-oslo.db-4.7.0/releasenotes/source/index.rst python-oslo.db-4.17.0/releasenotes/source/index.rst --- python-oslo.db-4.7.0/releasenotes/source/index.rst 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/source/index.rst 2017-01-18 14:09:32.000000000 +0000 @@ -6,4 +6,6 @@ :maxdepth: 1 unreleased + newton + mitaka liberty diff -Nru python-oslo.db-4.7.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po python-oslo.db-4.17.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po --- python-oslo.db-4.7.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,89 @@ +# Andi Chandler , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: oslo.db Release Notes 4.6.1.dev51\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-06-27 15:51+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-06-28 05:55+0000\n" +"Last-Translator: Andi Chandler \n" +"Language-Team: English (United Kingdom)\n" +"Language: en-GB\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +msgid "2.6.0-7" +msgstr "2.6.0-7" + +msgid "4.6.0" +msgstr "4.6.0" + +msgid "For details, please see the following LP:" +msgstr "For details, please see the following LP:" + +msgid "Introduce reno for deployer release notes." +msgstr "Introduce reno for deployer release notes." + +msgid "Liberty Series Release Notes" +msgstr "Liberty Series Release Notes" + +msgid "Mitaka Series Release Notes" +msgstr "Mitaka Series Release Notes" + +msgid "Other Notes" +msgstr "Other Notes" + +msgid "" +"The default value of ``max_overflow`` config option has been increased from " +"10 to 50 in order to allow OpenStack services heavily using DBs to better " +"handle spikes of concurrent requests and lower the probability of getting a " +"pool timeout issue." +msgstr "" +"The default value of ``max_overflow`` config option has been increased from " +"10 to 50 in order to allow OpenStack services heavily using DBs to better " +"handle spikes of concurrent requests and lower the probability of getting a " +"pool timeout issue." + +msgid "" +"This change potentially leads to increasing of the number of open " +"connections to an RDBMS server. Depending on the configuration, you may see " +"\"too many connections\" errors in logs of OpenStack services / RDBMS " +"server. The max limit of connections can be set by the means of these config " +"options:" +msgstr "" +"This change potentially leads to increasing of the number of open " +"connections to an RDBMS server. Depending on the configuration, you may see " +"\"too many connections\" errors in logs of OpenStack services / RDBMS " +"server. The max limit of connections can be set by the means of these config " +"options:" + +msgid "Unreleased Release Notes" +msgstr "Unreleased Release Notes" + +msgid "Upgrade Notes" +msgstr "Upgrade Notes" + +msgid "and the ML thread:" +msgstr "and the ML thread:" + +msgid "" +"http://dev.mysql.com/doc/refman/5.7/en/server-system-variables." +"html#sysvar_max_connections http://www.postgresql.org/docs/current/static/" +"runtime-config-connection.html#GUC-MAX-CONNECTIONS" +msgstr "" +"http://dev.mysql.com/doc/refman/5.7/en/server-system-variables." +"html#sysvar_max_connections http://www.postgresql.org/docs/current/static/" +"runtime-config-connection.html#GUC-MAX-CONNECTIONS" + +msgid "" +"http://lists.openstack.org/pipermail/openstack-dev/2015-December/082717.html" +msgstr "" +"http://lists.openstack.org/pipermail/openstack-dev/2015-December/082717.html" + +msgid "https://bugs.launchpad.net/oslo.db/+bug/1535375" +msgstr "https://bugs.launchpad.net/oslo.db/+bug/1535375" + +msgid "oslo.db Release Notes" +msgstr "oslo.db Release Notes" diff -Nru python-oslo.db-4.7.0/releasenotes/source/mitaka.rst python-oslo.db-4.17.0/releasenotes/source/mitaka.rst --- python-oslo.db-4.7.0/releasenotes/source/mitaka.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/source/mitaka.rst 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,6 @@ +=================================== + Mitaka Series Release Notes +=================================== + +.. release-notes:: + :branch: origin/stable/mitaka diff -Nru python-oslo.db-4.7.0/releasenotes/source/newton.rst python-oslo.db-4.17.0/releasenotes/source/newton.rst --- python-oslo.db-4.7.0/releasenotes/source/newton.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/releasenotes/source/newton.rst 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,6 @@ +============================= + Newton Series Release Notes +============================= + +.. release-notes:: + :branch: origin/stable/newton diff -Nru python-oslo.db-4.7.0/requirements.txt python-oslo.db-4.17.0/requirements.txt --- python-oslo.db-4.7.0/requirements.txt 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/requirements.txt 2017-01-18 14:09:32.000000000 +0000 @@ -2,14 +2,14 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 # Apache-2.0 -alembic>=0.8.0 # MIT -Babel>=1.3 # BSD +pbr>=1.8 # Apache-2.0 +alembic>=0.8.4 # MIT +debtcollector>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.config>=3.7.0 # Apache-2.0 -oslo.context>=0.2.0 # Apache-2.0 -oslo.utils>=3.5.0 # Apache-2.0 +oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 +oslo.context>=2.9.0 # Apache-2.0 +oslo.utils>=3.18.0 # Apache-2.0 SQLAlchemy<1.1.0,>=1.0.10 # MIT sqlalchemy-migrate>=0.9.6 # Apache-2.0 -stevedore>=1.5.0 # Apache-2.0 +stevedore>=1.17.1 # Apache-2.0 six>=1.9.0 # MIT diff -Nru python-oslo.db-4.7.0/setup.cfg python-oslo.db-4.17.0/setup.cfg --- python-oslo.db-4.7.0/setup.cfg 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/setup.cfg 2017-01-18 14:12:11.000000000 +0000 @@ -1,66 +1,63 @@ [metadata] name = oslo.db summary = Oslo Database library -description-file = - README.rst +description-file = + README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://wiki.openstack.org/wiki/Oslo#oslo.db -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 +home-page = http://docs.openstack.org/developer/oslo.db +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 [extras] -# So e.g. nova can test-depend on oslo.db[mysql] -mysql = - PyMySQL>=0.6.2 # MIT License -# or oslo.db[mysql-c] -mysql-c = - MySQL-python:python_version=='2.7' # GPL -# or oslo.db[postgresql] -postgresql = - psycopg2>=2.5 # LGPL/ZPL -# Dependencies for testing oslo.db itself. -test = - hacking<0.11,>=0.10.0 - coverage>=3.6 # Apache-2.0 - discover # BSD - doc8 # Apache-2.0 - eventlet!=0.18.3,>=0.18.2 # MIT - fixtures>=1.3.1 # Apache-2.0/BSD - mock>=1.2 # BSD - python-subunit>=0.0.18 # Apache-2.0/BSD - sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD - oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 - oslotest>=1.10.0 # Apache-2.0 - testrepository>=0.0.18 # Apache-2.0/BSD - testtools>=1.4.0 # MIT - tempest-lib>=0.14.0 # Apache-2.0 - reno>=0.1.1 # Apache2 -fixtures = - testresources>=0.2.4 # Apache-2.0/BSD - testscenarios>=0.4 # Apache-2.0/BSD +mysql = + PyMySQL>=0.7.6 # MIT License +mysql-c = + MySQL-python:python_version=='2.7' # GPL with FOSS exception +postgresql = + psycopg2>=2.5 # LGPL/ZPL +test = + hacking<0.11,>=0.10.0 + coverage>=4.0 # Apache-2.0 + doc8 # Apache-2.0 + eventlet!=0.18.3,>=0.18.2 # MIT + fixtures>=3.0.0 # Apache-2.0/BSD + mock>=2.0 # BSD + python-subunit>=0.0.18 # Apache-2.0/BSD + sphinx!=1.3b1,<1.4,>=1.2.1 # BSD + oslosphinx>=4.7.0 # Apache-2.0 + oslotest>=1.10.0 # Apache-2.0 + testrepository>=0.0.18 # Apache-2.0/BSD + testtools>=1.4.0 # MIT + os-testr>=0.8.0 # Apache-2.0 + reno>=1.8.0 # Apache-2.0 +fixtures = + testresources>=0.2.4 # Apache-2.0/BSD + testscenarios>=0.4 # Apache-2.0/BSD +pifpaf = + pifpaf>=0.10.0 # Apache-2.0 [files] -packages = - oslo_db +packages = + oslo_db [entry_points] -oslo.config.opts = - oslo.db = oslo_db.options:list_opts - oslo.db.concurrency = oslo_db.concurrency:list_opts - -oslo.db.migration = - alembic = oslo_db.sqlalchemy.migration_cli.ext_alembic:AlembicExtension - migrate = oslo_db.sqlalchemy.migration_cli.ext_migrate:MigrateExtension +oslo.config.opts = + oslo.db = oslo_db.options:list_opts + oslo.db.concurrency = oslo_db.concurrency:list_opts +oslo.db.migration = + alembic = oslo_db.sqlalchemy.migration_cli.ext_alembic:AlembicExtension + migrate = oslo_db.sqlalchemy.migration_cli.ext_migrate:MigrateExtension [wheel] universal = 1 @@ -90,5 +87,10 @@ [pbr] warnerrors = True autodoc_index_modules = True -autodoc_exclude_modules = - oslo_db.tests.* +autodoc_exclude_modules = + oslo_db.tests.* + +[egg_info] +tag_build = +tag_date = 0 + diff -Nru python-oslo.db-4.7.0/tools/run-pifpaf-tests.sh python-oslo.db-4.17.0/tools/run-pifpaf-tests.sh --- python-oslo.db-4.7.0/tools/run-pifpaf-tests.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/tools/run-pifpaf-tests.sh 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +# Replace mysql:// by mysql+pymysql:// and add sqlite +export OS_TEST_DBAPI_ADMIN_CONNECTION="${OS_TEST_DBAPI_ADMIN_CONNECTION/#mysql:/mysql+pymysql:};sqlite://" +echo $OS_TEST_DBAPI_ADMIN_CONNECTION +tools/pretty_tox.sh $* +TEST_EVENTLET=1 tools/pretty_tox.sh $* diff -Nru python-oslo.db-4.7.0/tools/tox_install.sh python-oslo.db-4.17.0/tools/tox_install.sh --- python-oslo.db-4.7.0/tools/tox_install.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.db-4.17.0/tools/tox_install.sh 2017-01-18 14:09:32.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE="$1" +shift 1 + +set -e + +# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get +# published to logs.openstack.org for easy debugging. +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ "$CONSTRAINTS_FILE" != http* ]]; then + CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" +fi +# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep +curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" + +pip install -c"$localfile" openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints "$localfile" -- "$CLIENT_NAME" + +pip install -c"$localfile" -U "$@" +exit $? diff -Nru python-oslo.db-4.7.0/tox.ini python-oslo.db-4.17.0/tox.ini --- python-oslo.db-4.7.0/tox.ini 2016-03-24 13:49:27.000000000 +0000 +++ python-oslo.db-4.17.0/tox.ini 2017-01-18 14:09:32.000000000 +0000 @@ -1,14 +1,28 @@ [tox] -minversion = 1.6 -envlist = py34,py27,pep8,pip-missing-reqs +minversion = 2.0 +envlist = py35,py34,py27,pep8,pip-missing-reqs [testenv] +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} whitelist_externals = bash env setenv = VIRTUAL_ENV={envdir} + BASECOMMAND=bash tools/pretty_tox.sh + BRANCH_NAME=master + CLIENT_NAME=oslo.db + + {postgresql,all}: PIFPAF_POSTGRESQL=pifpaf -g OS_TEST_DBAPI_ADMIN_CONNECTION run postgresql -- + {mysql,all}: PIFPAF_MYSQL=pifpaf -g OS_TEST_DBAPI_ADMIN_CONNECTION run mysql -- + {mysql,postgresql,all}: BASECOMMAND={toxinidir}/tools/run-pifpaf-tests.sh + deps = .[test,fixtures,mysql,postgresql] -commands = bash tools/pretty_tox.sh '{posargs}' + {postgresql,mysql,all}: .[pifpaf] + +commands = + {env:PIFPAF_MYSQL:} {env:PIFPAF_POSTGRESQL:} {env:BASECOMMAND:} '{posargs}' + +passenv = OS_TEST_DBAPI_ADMIN_CONNECTION [testenv:sqla_09] commands = pip install SQLAlchemy>=0.9.0,!=0.9.5,<1.0.0 @@ -46,7 +60,7 @@ # E123, E125 skipped as they are invalid PEP-8. ignore = E123,E125 show-source = True -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [hacking] import_exceptions =