Debian

Available patches from Ubuntu

To see Ubuntu differences wrt. to Debian, write down a grep-dctrl query identifying the packages you're interested in:
grep-dctrl -n -sPackage Sources.Debian
(e.g. -FPackage linux-ntfs or linux-ntfs)

Modified packages are listed below:

Debian ( Changelog | PTS | Bugs ) Ubuntu ( Changelog | txt | LP | Bugs ) | Diff from Ubuntu

Source: python-sqlalchemy-utils

python-sqlalchemy-utils (0.36.0-0ubuntu1) focal; urgency=medium [ Corey Bryant ] * d/gbp.conf: Update gbp configuration file. * d/control: Update Vcs-* links and maintainers. [ Sahid Orentino Ferdjaoui ] * New upstream release. -- Sahid Orentino Ferdjaoui <sahid.ferdjaoui@canonical.com> Thu, 19 Dec 2019 16:13:47 +0100

Modifications :
  1. Download patch docs/view.rst

    --- 0.32.21-2/docs/view.rst 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/docs/view.rst 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,22 @@ +View utilities +============== + +.. module:: sqlalchemy_utils + + +create_view +----------- + +.. autofunction:: create_view + + +create_materialized_view +------------------------ + +.. autofunction:: create_materialized_view + + +refresh_materialized_view +------------------------- + +.. autofunction:: refresh_materialized_view
  2. Download patch tests/test_views.py

    --- 0.32.21-2/tests/test_views.py 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/test_views.py 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,116 @@ +import pytest +import sqlalchemy as sa + +from sqlalchemy_utils import ( + create_materialized_view, + create_view, + refresh_materialized_view +) + + +@pytest.fixture +def Article(Base, User): + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String) + author_id = sa.Column(sa.Integer, sa.ForeignKey(User.id)) + author = sa.orm.relationship(User) + return Article + + +@pytest.fixture +def User(Base): + class User(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String) + return User + + +@pytest.fixture +def ArticleMV(Base, Article, User): + class ArticleMV(Base): + __table__ = create_materialized_view( + name='article_mv', + selectable=sa.select( + [ + Article.id, + Article.name, + User.id.label('author_id'), + User.name.label('author_name') + ], + from_obj=( + Article.__table__ + .join(User, Article.author_id == User.id) + ) + ), + metadata=Base.metadata, + indexes=[sa.Index('article_mv_id_idx', 'id')] + ) + return ArticleMV + + +@pytest.fixture +def ArticleView(Base, Article, User): + class ArticleView(Base): + __table__ = create_view( + name='article_view', + selectable=sa.select( + [ + Article.id, + Article.name, + User.id.label('author_id'), + User.name.label('author_name') + ], + from_obj=( + Article.__table__ + .join(User, Article.author_id == User.id) + ) + ), + metadata=Base.metadata + ) + return ArticleView + + +@pytest.fixture +def init_models(ArticleMV, ArticleView): + pass + + +@pytest.mark.usefixtures('postgresql_dsn') +class TestMaterializedViews: + def test_refresh_materialized_view( + self, + session, + Article, + User, + ArticleMV + ): + article = Article( + name='Some article', + author=User(name='Some user') + ) + session.add(article) + session.commit() + refresh_materialized_view(session, 'article_mv') + materialized = session.query(ArticleMV).first() + assert materialized.name == 'Some article' + assert materialized.author_name == 'Some user' + + def test_querying_view( + self, + session, + Article, + User, + ArticleView + ): + article = Article( + name='Some article', + author=User(name='Some user') + ) + session.add(article) + session.commit() + row = session.query(ArticleView).first() + assert row.name == 'Some article' + assert row.author_name == 'Some user'
  3. Download patch sqlalchemy_utils/functions/database.py
  4. Download patch sqlalchemy_utils/types/arrow.py

    --- 0.32.21-2/sqlalchemy_utils/types/arrow.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/arrow.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,6 +1,5 @@ from __future__ import absolute_import -from collections import Iterable from datetime import datetime import six @@ -9,6 +8,11 @@ from sqlalchemy import types from ..exceptions import ImproperlyConfigured from .scalar_coercible import ScalarCoercible +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable + arrow = None try: import arrow
  5. Download patch tests/functions/test_database.py

    --- 0.32.21-2/tests/functions/test_database.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/functions/test_database.py 2019-12-08 17:37:46.000000000 +0000 @@ -27,7 +27,7 @@ class TestDatabaseSQLiteMemory(object): assert database_exists(dsn) -@pytest.mark.usefixture('sqlite_none_database_dsn') +@pytest.mark.usefixtures('sqlite_none_database_dsn') class TestDatabaseSQLiteMemoryNoDatabaseString(object): def test_exists_memory_none_database(self, sqlite_none_database_dsn): assert database_exists(sqlite_none_database_dsn) @@ -35,7 +35,10 @@ class TestDatabaseSQLiteMemoryNoDatabase @pytest.mark.usefixtures('sqlite_file_dsn') class TestDatabaseSQLiteFile(DatabaseTest): - pass + def test_existing_non_sqlite_file(self, dsn): + database = sa.engine.url.make_url(dsn).database + open(database, 'w').close() + self.test_create_and_drop(dsn) @pytest.mark.skipif('pymysql is None') @@ -72,12 +75,22 @@ class TestDatabasePostgres(DatabaseTest) "TEMPLATE my_template" ) ) - dsn = 'postgres://{0}@localhost/db_test_sqlalchemy_util'.format( + dsn = 'postgresql://{0}@localhost/db_test_sqlalchemy_util'.format( postgresql_db_user ) create_database(dsn, template='my_template') +class TestDatabasePostgresPg8000(DatabaseTest): + + @pytest.fixture + def dsn(self, postgresql_db_user): + return 'postgresql+pg8000://{0}@localhost/{1}'.format( + postgresql_db_user, + 'db_to_test_create_and_drop_via_pg8000_driver' + ) + + @pytest.mark.usefixtures('postgresql_dsn') class TestDatabasePostgresWithQuotedName(DatabaseTest): @@ -95,7 +108,7 @@ class TestDatabasePostgresWithQuotedName 'TEMPLATE "my-template"' ) ) - dsn = 'postgres://{0}@localhost/db_test_sqlalchemy-util'.format( + dsn = 'postgresql://{0}@localhost/db_test_sqlalchemy-util'.format( postgresql_db_user ) create_database(dsn, template='my-template') @@ -104,7 +117,7 @@ class TestDatabasePostgresWithQuotedName class TestDatabasePostgresCreateDatabaseCloseConnection(object): def test_create_database_twice(self, postgresql_db_user): dsn_list = [ - 'postgres://{0}@localhost/db_test_sqlalchemy-util-a'.format( + 'postgresql://{0}@localhost/db_test_sqlalchemy-util-a'.format( postgresql_db_user ), 'postgres://{0}@localhost/db_test_sqlalchemy-util-b'.format( @@ -118,3 +131,12 @@ class TestDatabasePostgresCreateDatabase for dsn_item in dsn_list: drop_database(dsn_item) assert not database_exists(dsn_item) + + +@pytest.mark.usefixtures('mssql_dsn') +class TestDatabaseMssql(DatabaseTest): + + @pytest.fixture + def db_name(self): + pytest.importorskip('pyodbc') + return 'db_test_sqlalchemy_util'
  6. Download patch sqlalchemy_utils/generic.py

    --- 0.32.21-2/sqlalchemy_utils/generic.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/generic.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,4 +1,7 @@ -from collections import Iterable +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable import six import sqlalchemy as sa
  7. Download patch tests/types/test_composite.py

    --- 0.32.21-2/tests/types/test_composite.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/types/test_composite.py 2019-12-08 17:37:46.000000000 +0000 @@ -2,6 +2,7 @@ import pytest import sqlalchemy as sa from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm.session import close_all_sessions from sqlalchemy_utils import ( CompositeArray, @@ -324,7 +325,7 @@ class TestCompositeTypeWhenTypeAlreadyEx session.execute('DROP TABLE account') session.execute('DROP TYPE money_type') session.commit() - session.close_all() + close_all_sessions() connection.close() remove_composite_listeners() engine.dispose()
  8. Download patch tests/functions/test_analyze.py

    --- 0.32.21-2/tests/functions/test_analyze.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/functions/test_analyze.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -import pytest - -from sqlalchemy_utils import analyze - - -@pytest.mark.usefixtures('postgresql_dsn') -class TestAnalyzeWithPostgres(object): - - def test_runtime(self, session, connection, Article): - query = session.query(Article) - assert analyze(connection, query).runtime - - def test_node_types_with_join(self, session, connection, Article): - query = ( - session.query(Article) - .join(Article.category) - ) - analysis = analyze(connection, query) - assert analysis.node_types == [ - u'Hash Join', u'Seq Scan', u'Hash', u'Seq Scan' - ] - - def test_node_types_with_index_only_scan( - self, - session, - connection, - Article - ): - query = ( - session.query(Article.name) - .order_by(Article.name) - .limit(10) - ) - analysis = analyze(connection, query) - assert analysis.node_types == [u'Limit', u'Index Only Scan']
  9. Download patch sqlalchemy_utils/types/encrypted/encrypted_type.py
  10. Download patch tests/types/test_phonenumber.py

    --- 0.32.21-2/tests/types/test_phonenumber.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/types/test_phonenumber.py 2019-12-08 17:37:46.000000000 +0000 @@ -90,6 +90,18 @@ class TestPhoneNumber(object): assert number.international == u'+358 40 1234567' assert number.national == u'040 1234567' + def test_phone_number_attributes_for_short_code(self): + """ + For international and national shortcode remains the same, if we pass + short code to PhoneNumber library without giving check_region it will + raise exception + :return: + """ + number = PhoneNumber('72404', check_region=False) + assert number.e164 == u'+072404' + assert number.international == u'72404' + assert number.national == u'72404' + def test_phone_number_str_repr(self): number = PhoneNumber('+358401234567') if six.PY2:
  11. Download patch tests/types/encrypted/test_padding.py

    --- 0.32.21-2/tests/types/encrypted/test_padding.py 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/types/encrypted/test_padding.py 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,48 @@ +import pytest + +from sqlalchemy_utils.types.encrypted.padding import ( + InvalidPaddingError, + PKCS5Padding +) + + +class TestPkcs5Padding(object): + def setup_method(self): + self.BLOCK_SIZE = 8 + self.padder = PKCS5Padding(self.BLOCK_SIZE) + + def test_various_lengths_roundtrip(self): + for l in range(0, 3 * self.BLOCK_SIZE): + val = b'*' * l + padded = self.padder.pad(val) + unpadded = self.padder.unpad(padded) + assert val == unpadded, 'Round trip error for length %d' % l + + def test_invalid_unpad(self): + with pytest.raises(InvalidPaddingError): + self.padder.unpad(None) + with pytest.raises(InvalidPaddingError): + self.padder.unpad(b'') + with pytest.raises(InvalidPaddingError): + self.padder.unpad(b'\01') + with pytest.raises(InvalidPaddingError): + self.padder.unpad((b'*' * (self.BLOCK_SIZE - 1)) + b'\00') + with pytest.raises(InvalidPaddingError): + self.padder.unpad((b'*' * self.BLOCK_SIZE) + b'\01') + + def test_pad_longer_than_block(self): + with pytest.raises(InvalidPaddingError): + self.padder.unpad( + 'x' * (self.BLOCK_SIZE - 1) + + chr(self.BLOCK_SIZE + 1) * (self.BLOCK_SIZE + 1) + ) + + def test_incorrect_padding(self): + # Hard-coded for blocksize of 8 + assert self.padder.unpad(b'1234\04\04\04\04') == b'1234' + with pytest.raises(InvalidPaddingError): + self.padder.unpad(b'1234\02\04\04\04') + with pytest.raises(InvalidPaddingError): + self.padder.unpad(b'1234\04\02\04\04') + with pytest.raises(InvalidPaddingError): + self.padder.unpad(b'1234\04\04\02\04')
  12. Download patch sqlalchemy_utils/models.py

    --- 0.32.21-2/sqlalchemy_utils/models.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/models.py 2019-12-08 17:37:46.000000000 +0000 @@ -55,7 +55,7 @@ def _generic_repr_method(self, fields): def generic_repr(*fields): - """Adds generic ``__repr__()`` method to a decalrative SQLAlchemy model. + """Adds generic ``__repr__()`` method to a declarative SQLAlchemy model. In case if some fields are not loaded from a database, it doesn't force their loading and instead repesents them as ``<not loaded>``.
  13. Download patch sqlalchemy_utils/primitives/currency.py

    --- 0.32.21-2/sqlalchemy_utils/primitives/currency.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/primitives/currency.py 2019-12-08 17:37:46.000000000 +0000 @@ -26,7 +26,8 @@ class Currency(object): Currency(Currency('USD')).code # 'USD' - Currency always validates the given code. + Currency always validates the given code if you use at least the optional + dependency list 'babel', otherwise no validation are performed. :: @@ -75,6 +76,9 @@ class Currency(object): i18n.babel.Locale('en').currencies[code] except KeyError: raise ValueError("'{0}' is not valid currency code.".format(code)) + except AttributeError: + # As babel is optional, we may raise an AttributeError accessing it + pass @property def symbol(self):
  14. Download patch docs/data_types.rst

    --- 0.32.21-2/docs/data_types.rst 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/docs/data_types.rst 2019-12-08 17:37:46.000000000 +0000 @@ -74,7 +74,7 @@ EmailType EncryptedType ------------- -.. module:: sqlalchemy_utils.types.encrypted +.. module:: sqlalchemy_utils.types.encrypted.encrypted_type .. autoclass:: EncryptedType @@ -183,4 +183,3 @@ WeekDaysType .. module:: sqlalchemy_utils.types.weekdays .. autoclass:: WeekDaysType -
  15. Download patch sqlalchemy_utils/types/timezone.py

    --- 0.32.21-2/sqlalchemy_utils/types/timezone.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/timezone.py 2019-12-08 17:37:46.000000000 +0000 @@ -38,10 +38,10 @@ class TimezoneType(types.TypeDecorator, if backend == 'dateutil': try: from dateutil.tz import tzfile - from dateutil.zoneinfo import gettz + from dateutil.zoneinfo import get_zonefile_instance self.python_type = tzfile - self._to = gettz + self._to = get_zonefile_instance().zones.get self._from = lambda x: six.text_type(x._filename) except ImportError:
  16. Download patch sqlalchemy_utils/types/__init__.py

    --- 0.32.21-2/sqlalchemy_utils/types/__init__.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/__init__.py 2019-12-08 17:37:46.000000000 +0000 @@ -8,7 +8,7 @@ from .color import ColorType # noqa from .country import CountryType # noqa from .currency import CurrencyType # noqa from .email import EmailType # noqa -from .encrypted import EncryptedType # noqa +from .encrypted.encrypted_type import EncryptedType # noqa from .ip_address import IPAddressType # noqa from .json import JSONType # noqa from .locale import LocaleType # noqa @@ -28,6 +28,7 @@ from .phone_number import ( # noqa from .range import ( # noqa DateRangeType, DateTimeRangeType, + Int8RangeType, IntRangeType, NumericRangeType )
  17. Download patch debian/control

    --- 0.32.21-2/debian/control 2019-09-03 07:21:23.000000000 +0000 +++ 0.36.0-0ubuntu1/debian/control 2019-12-19 15:13:47.000000000 +0000 @@ -1,7 +1,8 @@ Source: python-sqlalchemy-utils Section: python Priority: optional -Maintainer: Debian OpenStack <team+openstack@tracker.debian.org> +Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> +XSBC-Original-Maintainer: Debian OpenStack <team+openstack@tracker.debian.org> Uploaders: Thomas Goirand <zigo@debian.org>, Build-Depends: @@ -23,8 +24,8 @@ Build-Depends-Indep: python3-sqlalchemy, python3-tz, Standards-Version: 4.4.0 -Vcs-Browser: https://salsa.debian.org/openstack-team/python/python-sqlalchemy-utils -Vcs-Git: https://salsa.debian.org/openstack-team/python/python-sqlalchemy-utils.git +Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-sqlalchemy-utils +Vcs-Git: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-sqlalchemy-utils Homepage: https://github.com/kvesteri/sqlalchemy-utils Testsuite: autopkgtest-pkg-python
  18. Download patch .travis.yml

    --- 0.32.21-2/.travis.yml 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/.travis.yml 2019-12-08 17:37:46.000000000 +0000 @@ -1,9 +1,14 @@ -sudo: false language: python +sudo: required +dist: xenial addons: postgresql: "9.4" +services: + - docker + - mysql + before_script: - psql -c 'create database sqlalchemy_utils_test;' -U postgres - psql -c 'create extension hstore;' -U postgres -d sqlalchemy_utils_test @@ -14,23 +19,21 @@ matrix: - python: 2.7 env: - "TOXENV=py27" - - python: 3.3 - env: - - "TOXENV=py33" - - python: 3.4 - env: - - "TOXENV=py34" - python: 3.5 env: - "TOXENV=py35" - python: 3.6 env: - "TOXENV=py36" - - python: 3.6 + - python: 3.7 + env: + - "TOXENV=py37" + - python: 3.7 env: - "TOXENV=lint" install: + - source $TRAVIS_BUILD_DIR/.travis/install_mssql.sh - pip install tox script:
  19. Download patch sqlalchemy_utils/expressions.py

    --- 0.32.21-2/sqlalchemy_utils/expressions.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/expressions.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,74 +1,12 @@ import sqlalchemy as sa from sqlalchemy.dialects import postgresql from sqlalchemy.ext.compiler import compiles -from sqlalchemy.sql.expression import ( - _literal_as_text, - ClauseElement, - ColumnElement, - Executable, - FunctionElement -) +from sqlalchemy.sql.expression import ColumnElement, FunctionElement from sqlalchemy.sql.functions import GenericFunction from .functions.orm import quote -class explain(Executable, ClauseElement): - """ - Define EXPLAIN element. - - http://www.postgresql.org/docs/devel/static/sql-explain.html - """ - def __init__( - self, - stmt, - analyze=False, - verbose=False, - costs=True, - buffers=False, - timing=True, - format='text' - ): - self.statement = _literal_as_text(stmt) - self.analyze = analyze - self.verbose = verbose - self.costs = costs - self.buffers = buffers - self.timing = timing - self.format = format - - -class explain_analyze(explain): - def __init__(self, stmt, **kwargs): - super(explain_analyze, self).__init__( - stmt, - analyze=True, - **kwargs - ) - - -@compiles(explain, 'postgresql') -def pg_explain(element, compiler, **kw): - text = "EXPLAIN " - options = [] - if element.analyze: - options.append('ANALYZE true') - if not element.timing: - options.append('TIMING false') - if element.buffers: - options.append('BUFFERS true') - if element.format != 'text': - options.append('FORMAT %s' % element.format) - if element.verbose: - options.append('VERBOSE true') - if not element.costs: - options.append('COSTS false') - if options: - text += '(%s) ' % ', '.join(options) - text += compiler.process(element.statement) - return text - - class array_get(FunctionElement): name = 'array_get' @@ -112,27 +50,6 @@ def compile_json_array_length(element, c return "%s(%s)" % (element.name, compiler.process(element.clauses)) -class array_agg(GenericFunction): - name = 'array_agg' - type = postgresql.ARRAY - - def __init__(self, arg, default=None, **kw): - self.type = postgresql.ARRAY(arg.type) - self.default = default - GenericFunction.__init__(self, arg, **kw) - - -@compiles(array_agg, 'postgresql') -def compile_array_agg(element, compiler, **kw): - compiled = "%s(%s)" % (element.name, compiler.process(element.clauses)) - if element.default is None: - return compiled - return str(sa.func.coalesce( - sa.text(compiled), - sa.cast(postgresql.array(element.default), element.type) - ).compile(compiler)) - - class Asterisk(ColumnElement): def __init__(self, selectable): self.selectable = selectable
  20. Download patch tests/test_translation_hybrid.py

    --- 0.32.21-2/tests/test_translation_hybrid.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/test_translation_hybrid.py 2019-12-08 17:37:46.000000000 +0000 @@ -43,7 +43,7 @@ class TestTranslationHybrid(object): def test_custom_default_value(self, City, translation_hybrid): translation_hybrid.default_value = 'Some value' city = City() - assert city.name is 'Some value' + assert city.name == 'Some value' def test_fall_back_to_default_translation(self, City, translation_hybrid): city = City(name_translations={'en': 'Helsinki'}) @@ -59,6 +59,17 @@ class TestTranslationHybrid(object): assert city.name == 'Helsinki' + def test_fallback_to_attr_dependent_locale(self, City, translation_hybrid): + translation_hybrid.current_locale = 'en' + translation_hybrid.default_locale = ( + lambda obj, attr: sorted(getattr(obj, attr).keys())[0] + ) + city = City(name_translations={}) + city.name_translations['fi'] = 'Helsinki' + assert city.name == 'Helsinki' + city.name_translations['de'] = 'Stadt Helsinki' + assert city.name == 'Stadt Helsinki' + @pytest.mark.parametrize( ('name_translations', 'name'), (
  21. Download patch tox.ini

    --- 0.32.21-2/tox.ini 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tox.ini 2019-12-08 17:37:46.000000000 +0000 @@ -1,5 +1,5 @@ [tox] -envlist = py27, py33, py34, py35, py36, lint +envlist = py27, py35, py36, py37, lint [testenv] commands = @@ -11,18 +11,15 @@ passenv = SQLALCHEMY_UTILS_TEST_DB SQLAL [testenv:py27] recreate = True -[testenv:py33] -recreate = True - -[testenv:py34] -recreate = True - [testenv:py35] recreate = True [testenv:py36] recreate = True +[testenv:py37] +recreate = True + [testenv:lint] recreate = True commands =
  22. Download patch conftest.py

    --- 0.32.21-2/conftest.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/conftest.py 2019-12-08 17:37:46.000000000 +0000 @@ -7,6 +7,7 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base, synonym_for from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm.session import close_all_sessions from sqlalchemy_utils import ( aggregates, coercion_listener, @@ -54,7 +55,7 @@ def mysql_db_user(): @pytest.fixture def postgresql_dsn(postgresql_db_user, db_name): - return 'postgres://{0}@localhost/{1}'.format(postgresql_db_user, db_name) + return 'postgresql://{0}@localhost/{1}'.format(postgresql_db_user, db_name) @pytest.fixture @@ -73,21 +74,47 @@ def sqlite_none_database_dsn(): @pytest.fixture -def sqlite_file_dsn(): +def sqlite_file_dsn(db_name): return 'sqlite:///{0}.db'.format(db_name) @pytest.fixture +def mssql_db_user(): + return os.environ.get('SQLALCHEMY_UTILS_TEST_MSSQL_USER', 'sa') + + +@pytest.fixture +def mssql_db_password(): + return os.environ.get('SQLALCHEMY_UTILS_TEST_MSSQL_PASSWORD', + 'Strong!Passw0rd') + + +@pytest.fixture +def mssql_db_driver(): + driver = os.environ.get('SQLALCHEMY_UTILS_TEST_MSSQL_DRIVER', + 'ODBC Driver 17 for SQL Server') + return driver.replace(' ', '+') + + +@pytest.fixture +def mssql_dsn(mssql_db_user, mssql_db_password, mssql_db_driver, db_name): + return 'mssql+pyodbc://{0}:{1}@localhost/{2}?driver={3}'\ + .format(mssql_db_user, mssql_db_password, db_name, mssql_db_driver) + + +@pytest.fixture def dsn(request): if 'postgresql_dsn' in request.fixturenames: - return request.getfuncargvalue('postgresql_dsn') + return request.getfixturevalue('postgresql_dsn') elif 'mysql_dsn' in request.fixturenames: - return request.getfuncargvalue('mysql_dsn') + return request.getfixturevalue('mysql_dsn') + elif 'mssql_dsn' in request.fixturenames: + return request.getfixturevalue('mssql_dsn') elif 'sqlite_file_dsn' in request.fixturenames: - return request.getfuncargvalue('sqlite_file_dsn') + return request.getfixturevalue('sqlite_file_dsn') elif 'sqlite_memory_dsn' in request.fixturenames: pass # Return default - return request.getfuncargvalue('sqlite_memory_dsn') + return request.getfixturevalue('sqlite_memory_dsn') @pytest.fixture @@ -192,7 +219,7 @@ def session(request, engine, connection, def teardown(): aggregates.manager.reset() - session.close_all() + close_all_sessions() Base.metadata.drop_all(connection) remove_composite_listeners() connection.close()
  23. Download patch .travis/install_mssql.sh

    --- 0.32.21-2/.travis/install_mssql.sh 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/.travis/install_mssql.sh 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +wget http://www.unixodbc.org/unixODBC-2.3.1.tar.gz +tar xvf unixODBC-2.3.1.tar.gz +cd unixODBC-2.3.1/ +./configure --disable-gui \ + --disable-drivers \ + --enable-iconv \ + --with-iconv-char-enc=UTF8 \ + --with-iconv-ucode-enc=UTF16LE +make +sudo make install +sudo ldconfig + +sudo su <<EOF +curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list > /etc/apt/sources.list.d/mssql-release.list +EOF + +sudo apt-get update +sudo ACCEPT_EULA=Y apt-get install msodbcsql17 + +docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Strong!Passw0rd' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
  24. Download patch sqlalchemy_utils/i18n.py

    --- 0.32.21-2/sqlalchemy_utils/i18n.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/i18n.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,3 +1,5 @@ +import inspect + import six import sqlalchemy as sa from sqlalchemy.ext.compiler import compiles @@ -12,18 +14,35 @@ try: except ImportError: babel = None -try: - from flask_babel import get_locale -except ImportError: - def get_locale(): + +def get_locale(): + try: + return babel.Locale('en') + except AttributeError: + # As babel is optional, we may raise an AttributeError accessing it raise ImproperlyConfigured( - 'Could not load get_locale function from Flask-Babel. Either ' - 'install Flask-Babel or make a similar function and override it ' + 'Could not load get_locale function using Babel. Either ' + 'install Babel or make a similar function and override it ' 'in this module.' ) -def cast_locale(obj, locale): +if six.PY2: + def get_args_count(func): + if ( + callable(func) and + not inspect.isfunction(func) and + not inspect.ismethod(func) + ): + func = func.__call__ + args = inspect.getargspec(func).args + return len(args) - 1 if inspect.ismethod(func) else len(args) +else: + def get_args_count(func): + return len(inspect.signature(func).parameters) + + +def cast_locale(obj, locale, attr): """ Cast given locale to string. Supports also callbacks that return locales. @@ -33,24 +52,28 @@ def cast_locale(obj, locale): Locale object or string or callable that returns a locale. """ if callable(locale): - try: + args_count = get_args_count(locale) + if args_count == 0: locale = locale() - except TypeError: + elif args_count == 1: locale = locale(obj) + elif args_count == 2: + locale = locale(obj, attr.key) if isinstance(locale, babel.Locale): return str(locale) return locale class cast_locale_expr(ColumnElement): - def __init__(self, cls, locale): + def __init__(self, cls, locale, attr): self.cls = cls self.locale = locale + self.attr = attr @compiles(cast_locale_expr) def compile_cast_locale_expr(element, compiler, **kw): - locale = cast_locale(element.cls, element.locale) + locale = cast_locale(element.cls, element.locale, element.attr) if isinstance(locale, six.string_types): return "'{0}'".format(locale) return compiler.process(locale) @@ -74,13 +97,11 @@ class TranslationHybrid(object): is no translation found for default locale it returns None. """ def getter(obj): - current_locale = cast_locale(obj, self.current_locale) + current_locale = cast_locale(obj, self.current_locale, attr) try: return getattr(obj, attr.key)[current_locale] except (TypeError, KeyError): - default_locale = cast_locale( - obj, self.default_locale - ) + default_locale = cast_locale(obj, self.default_locale, attr) try: return getattr(obj, attr.key)[default_locale] except (TypeError, KeyError): @@ -91,15 +112,15 @@ class TranslationHybrid(object): def setter(obj, value): if getattr(obj, attr.key) is None: setattr(obj, attr.key, {}) - locale = cast_locale(obj, self.current_locale) + locale = cast_locale(obj, self.current_locale, attr) getattr(obj, attr.key)[locale] = value return setter def expr_factory(self, attr): def expr(cls): cls_attr = getattr(cls, attr.key) - current_locale = cast_locale_expr(cls, self.current_locale) - default_locale = cast_locale_expr(cls, self.default_locale) + current_locale = cast_locale_expr(cls, self.current_locale, attr) + default_locale = cast_locale_expr(cls, self.default_locale, attr) return sa.func.coalesce( cls_attr[current_locale], cls_attr[default_locale]
  25. Download patch CHANGES.rst

    --- 0.32.21-2/CHANGES.rst 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/CHANGES.rst 2019-12-08 17:37:46.000000000 +0000 @@ -4,6 +4,124 @@ Changelog Here you can see the full list of changes between each SQLAlchemy-Utils release. +0.36.0 (2019-12-08) +^^^^^^^^^^^^^^^^^^^ + +- Removed explain and explain_analyze due to the internal changes in SQLAlchemy version 1.3. + + +0.35.0 (2019-11-01) +^^^^^^^^^^^^^^^^^^^ + +- Removed some deprecation warnings +- Added Int8RangeType (#401, pull request courtesy of lpsinger) + + +0.34.2 (2019-08-20) +^^^^^^^^^^^^^^^^^^^ + +- Remove ABC deprecation warnings (#386, pull request courtesy of VizualAbstract) + + +0.34.1 (2019-07-15) +^^^^^^^^^^^^^^^^^^^ + +- Remove deprecation warnings (#379, pull request courtesy of Le-Stagiaire) +- Drop py34 support + + +0.34.0 (2019-06-09) +^^^^^^^^^^^^^^^^^^^ + + +- Removed array_agg compilation which was never a good idea and collided with the latest version of SA. (#374) +- Removed deprecation warnings (#373, pull request courtesy of pbasista) + + +0.33.12 (2019-02-02) +^^^^^^^^^^^^^^^^^^^^ + +- Added ordering support for Country primitive (#361, pull request courtesy of TrilceAC) + + +0.33.11 (2019-01-13) +^^^^^^^^^^^^^^^^^^^^ + +- Added support for creating and dropping a PostgreSQL database when using pg8000 driver (#303, pull request courtesy of mohaseeb) + + +0.33.10 (2018-12-27) +^^^^^^^^^^^^^^^^^^^^ + +- Removed optional dependency to Flask-Babel. Now using Babel instead. (#333, pull request courtesy of aveuiller) + + +0.33.9 (2018-11-19) +^^^^^^^^^^^^^^^^^^^ + +- Fixed SQLite database_exists to check for correct file format (#306, pull request courtesy of jomasti) + + +0.33.8 (2018-11-19) +^^^^^^^^^^^^^^^^^^^ + +- Added support of short-code in PhoneNumberType (#348, pull request courtesy of anandtripathi5) + + +0.33.7 (2018-11-19) +^^^^^^^^^^^^^^^^^^^ + +- Added MSSQL support for create_database and drop_database (#337, pull request courtesy of jomasti) + + +0.33.6 (2018-10-14) +^^^^^^^^^^^^^^^^^^^ + +- Fixed passlib compatibility issue (again) (#342) +- Added support for SQL VIEWs + + +0.33.5 (2018-09-19) +^^^^^^^^^^^^^^^^^^^ + +- Added optional attr parameter for locale calleble in TranslationHybrid +- Fixed an issue with PasswordType so that it is compatible with older versions of passlib (#342) + + +0.33.4 (2018-09-11) +^^^^^^^^^^^^^^^^^^^ + +- Made PasswordType use `hash` function instead of deprecated `encrypt` function (#341, pull request courtesy of libre-man) + + +0.33.3 (2018-04-29) +^^^^^^^^^^^^^^^^^^^ + +- Added new AesGcmEngine (#322, pull request courtesy of manishahluwalia) + + +0.33.2 (2018-04-02) +^^^^^^^^^^^^^^^^^^^ + +- Added support for universal wheels (#312, pull request courtesy of nsoranzo) +- Fixed usage of template0 and template1 with postgres database functions. (#286, pull request courtesy of funkybob) + + +0.33.1 (2018-03-19) +^^^^^^^^^^^^^^^^^^^ + +- Fixed EncryptedType for Oracle padding attack (#316, pull request courtesy of manishahluwalia) + + +0.33.0 (2018-02-18) +^^^^^^^^^^^^^^^^^^^ + +- Added support for materialized views in PostgreSQL +- Added Ltree.descendant_of and Ltree.ancestor_of (#311, pull request courtesy of kageurufu) +- Dropped Python 3.3 support +- Fixed EncryptedType padding (#301, pull request courtesy of konstantinoskostis) + + 0.32.21 (2017-11-11) ^^^^^^^^^^^^^^^^^^^^
  26. Download patch sqlalchemy_utils/types/ltree.py

    --- 0.32.21-2/sqlalchemy_utils/types/ltree.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/ltree.py 2019-12-08 17:37:46.000000000 +0000 @@ -18,16 +18,16 @@ class LtreeType(types.Concatenable, type :: - from sqlalchemy_utils import LtreeType + from sqlalchemy_utils import LtreeType, Ltree class DocumentSection(Base): __tablename__ = 'document_section' - id = sa.Column(sa.Integer, autoincrement=True) + id = sa.Column(sa.Integer, autoincrement=True, primary_key=True) path = sa.Column(LtreeType) - section = DocumentSection(name='Countries.Finland') + section = DocumentSection(path=Ltree('Countries.Finland')) session.add(section) session.commit()
  27. Download patch sqlalchemy_utils/functions/__init__.py

    --- 0.32.21-2/sqlalchemy_utils/functions/__init__.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/functions/__init__.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,5 +1,4 @@ from .database import ( # noqa - analyze, create_database, database_exists, drop_database,
  28. Download patch sqlalchemy_utils/types/encrypted/__init__.py

    --- 0.32.21-2/sqlalchemy_utils/types/encrypted/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/encrypted/__init__.py 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1 @@ +# Module for encrypted type
  29. Download patch sqlalchemy_utils/types/password.py

    --- 0.32.21-2/sqlalchemy_utils/types/password.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/password.py 2019-12-08 17:37:46.000000000 +0000 @@ -158,10 +158,17 @@ class PasswordType(types.TypeDecorator, # Construct the passlib crypt context. self.context = LazyCryptContext(**kwargs) - self._max_length = max_length @property + def hashing_method(self): + return ( + 'hash' + if hasattr(self.context, 'hash') + else 'encrypt' + ) + + @property def length(self): """Get column length.""" if self._max_length is None: @@ -205,21 +212,24 @@ class PasswordType(types.TypeDecorator, def process_bind_param(self, value, dialect): if isinstance(value, Password): - # If were given a password secret; encrypt it. + # If were given a password secret; hash it. if value.secret is not None: - return self.context.encrypt(value.secret).encode('utf8') + return self._hash(value.secret).encode('utf8') # Value has already been hashed. return value.hash if isinstance(value, six.string_types): # Assume value has not been hashed. - return self.context.encrypt(value).encode('utf8') + return self._hash(value).encode('utf8') def process_result_value(self, value, dialect): if value is not None: return Password(value, self.context) + def _hash(self, value): + return getattr(self.context, self.hashing_method)(value) + def _coerce(self, value): if value is None: @@ -227,16 +237,16 @@ class PasswordType(types.TypeDecorator, if not isinstance(value, Password): # Hash the password using the default scheme. - value = self.context.encrypt(value).encode('utf8') + value = self._hash(value).encode('utf8') return Password(value, context=self.context) else: # If were given a password object; ensure the context is right. value.context = weakref.proxy(self.context) - # If were given a password secret; encrypt it. + # If were given a password secret; hash it. if value.secret is not None: - value.hash = self.context.encrypt(value.secret).encode('utf8') + value.hash = self._hash(value.secret).encode('utf8') value.secret = None return value
  30. Download patch sqlalchemy_utils/types/phone_number.py

    --- 0.32.21-2/sqlalchemy_utils/types/phone_number.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/phone_number.py 2019-12-08 17:37:46.000000000 +0000 @@ -29,7 +29,7 @@ class PhoneNumberParseException(NumberPa @str_coercible class PhoneNumber(BasePhoneNumber): - ''' + """ Extends a PhoneNumber class from `Python phonenumbers library`_. Adds different phone number formats to attributes, so they can be easily used in templates. Phone number validation method is also implemented. @@ -72,8 +72,12 @@ class PhoneNumber(BasePhoneNumber): String representation of the phone number. :param region: Region of the phone number. - ''' - def __init__(self, raw_number, region=None): + :param check_region: + Whether to check the supplied region parameter; + should always be True for external callers. + Can be useful for short codes or toll free + """ + def __init__(self, raw_number, region=None, check_region=True): # Bail if phonenumbers is not found. if phonenumbers is None: raise ImproperlyConfigured( @@ -81,7 +85,11 @@ class PhoneNumber(BasePhoneNumber): ) try: - self._phone_number = phonenumbers.parse(raw_number, region) + self._phone_number = phonenumbers.parse( + raw_number, + region, + _check_region=check_region + ) except NumberParseException as e: # Wrap exception so SQLAlchemy doesn't swallow it as a # StatementError
  31. Download patch sqlalchemy_utils/primitives/ltree.py

    --- 0.32.21-2/sqlalchemy_utils/primitives/ltree.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/primitives/ltree.py 2019-12-08 17:37:46.000000000 +0000 @@ -36,7 +36,7 @@ class Ltree(object): :: Ltree.validate('1.2.3') - Ltree.validate(None) # raises ValueError + Ltree.validate(None) # raises TypeError Ltree supports equality operators. @@ -59,7 +59,7 @@ class Ltree(object): :: - assert len(Ltree('1.2')) 2 + assert len(Ltree('1.2')) == 2 assert len(Ltree('some.one.some.where')) # 4 @@ -128,6 +128,28 @@ class Ltree(object): return index raise ValueError('subpath not found') + def descendant_of(self, other): + """ + is left argument a descendant of right (or equal)? + + :: + + assert Ltree('1.2.3.4.5').descendant_of('1.2.3') + """ + subpath = self[:len(Ltree(other))] + return subpath == other + + def ancestor_of(self, other): + """ + is left argument an ancestor of right (or equal)? + + :: + + assert Ltree('1.2.3').ancestor_of('1.2.3.4.5') + """ + subpath = Ltree(other)[:len(self)] + return subpath == self + def __getitem__(self, key): if isinstance(key, int): return Ltree(self.path.split('.')[key])
  32. Download patch docs/index.rst

    --- 0.32.21-2/docs/index.rst 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/docs/index.rst 2019-12-08 17:37:46.000000000 +0000 @@ -21,5 +21,6 @@ SQLAlchemy-Utils provides custom data ty orm_helpers utility_classes models + view testing license
  33. Download patch setup.cfg

    --- 0.32.21-2/setup.cfg 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/setup.cfg 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1
  34. Download patch sqlalchemy_utils/types/scalar_coercible.py

    --- 0.32.21-2/sqlalchemy_utils/types/scalar_coercible.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/scalar_coercible.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,6 +1,6 @@ class ScalarCoercible(object): def _coerce(self, value): - raise NotImplemented + raise NotImplementedError def coercion_listener(self, target, value, oldvalue, initiator): return self._coerce(value)
  35. Download patch sqlalchemy_utils/types/encrypted/padding.py

    --- 0.32.21-2/sqlalchemy_utils/types/encrypted/padding.py 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/encrypted/padding.py 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,145 @@ +import six + + +class InvalidPaddingError(Exception): + pass + + +class Padding(object): + """Base class for padding and unpadding.""" + + def __init__(self, block_size): + self.block_size = block_size + + def pad(value): + raise NotImplementedError('Subclasses must implement this!') + + def unpad(value): + raise NotImplementedError('Subclasses must implement this!') + + +class PKCS5Padding(Padding): + """Provide PKCS5 padding and unpadding.""" + + def pad(self, value): + if not isinstance(value, six.binary_type): + value = value.encode() + padding_length = (self.block_size - len(value) % self.block_size) + padding_sequence = padding_length * six.b(chr(padding_length)) + value_with_padding = value + padding_sequence + + return value_with_padding + + def unpad(self, value): + # Perform some input validations. + # In case of error, we throw a generic InvalidPaddingError() + if not value or len(value) < self.block_size: + # PKCS5 padded output will always be at least 1 block size + raise InvalidPaddingError() + if len(value) % self.block_size != 0: + # PKCS5 padded output will be a multiple of the block size + raise InvalidPaddingError() + if isinstance(value, six.binary_type): + padding_length = value[-1] + if isinstance(value, six.string_types): + padding_length = ord(value[-1]) + if padding_length == 0 or padding_length > self.block_size: + raise InvalidPaddingError() + + def convert_byte_or_char_to_number(x): + return ord(x) if isinstance(x, six.string_types) else x + if any([padding_length != convert_byte_or_char_to_number(x) + for x in value[-padding_length:]]): + raise InvalidPaddingError() + + value_without_padding = value[0:-padding_length] + + return value_without_padding + + +class OneAndZeroesPadding(Padding): + """Provide the one and zeroes padding and unpadding. + + This mechanism pads with 0x80 followed by zero bytes. + For unpadding it strips off all trailing zero bytes and the 0x80 byte. + """ + + BYTE_80 = 0x80 + BYTE_00 = 0x00 + + def pad(self, value): + if not isinstance(value, six.binary_type): + value = value.encode() + padding_length = (self.block_size - len(value) % self.block_size) + one_part_bytes = six.b(chr(self.BYTE_80)) + zeroes_part_bytes = (padding_length - 1) * six.b(chr(self.BYTE_00)) + padding_sequence = one_part_bytes + zeroes_part_bytes + value_with_padding = value + padding_sequence + + return value_with_padding + + def unpad(self, value): + value_without_padding = value.rstrip(six.b(chr(self.BYTE_00))) + value_without_padding = value_without_padding.rstrip( + six.b(chr(self.BYTE_80))) + + return value_without_padding + + +class ZeroesPadding(Padding): + """Provide zeroes padding and unpadding. + + This mechanism pads with 0x00 except the last byte equals + to the padding length. For unpadding it reads the last byte + and strips off that many bytes. + """ + + BYTE_00 = 0x00 + + def pad(self, value): + if not isinstance(value, six.binary_type): + value = value.encode() + padding_length = (self.block_size - len(value) % self.block_size) + zeroes_part_bytes = (padding_length - 1) * six.b(chr(self.BYTE_00)) + last_part_bytes = six.b(chr(padding_length)) + padding_sequence = zeroes_part_bytes + last_part_bytes + value_with_padding = value + padding_sequence + + return value_with_padding + + def unpad(self, value): + if isinstance(value, six.binary_type): + padding_length = value[-1] + if isinstance(value, six.string_types): + padding_length = ord(value[-1]) + value_without_padding = value[0:-padding_length] + + return value_without_padding + + +class NaivePadding(Padding): + """Naive padding and unpadding using '*'. + + The class is provided only for backwards compatibility. + """ + + CHARACTER = six.b('*') + + def pad(self, value): + num_of_bytes = (self.block_size - len(value) % self.block_size) + value_with_padding = value + num_of_bytes * self.CHARACTER + + return value_with_padding + + def unpad(self, value): + value_without_padding = value.rstrip(self.CHARACTER) + + return value_without_padding + + +PADDING_MECHANISM = { + 'pkcs5': PKCS5Padding, + 'oneandzeroes': OneAndZeroesPadding, + 'zeroes': ZeroesPadding, + 'naive': NaivePadding +}
  36. Download patch sqlalchemy_utils/types/encrypted.py
  37. Download patch sqlalchemy_utils/observer.py

    --- 0.32.21-2/sqlalchemy_utils/observer.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/observer.py 2019-12-08 17:37:46.000000000 +0000 @@ -173,7 +173,7 @@ in the decorator. """ import itertools -from collections import defaultdict, Iterable, namedtuple +from collections import defaultdict, namedtuple import sqlalchemy as sa @@ -181,6 +181,11 @@ from .functions import getdotattr, has_c from .path import AttrPath from .utils import is_sequence +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable + Callback = namedtuple('Callback', ['func', 'backref', 'fullpath'])
  38. Download patch setup.py

    --- 0.32.21-2/setup.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/setup.py 2019-12-08 17:37:46.000000000 +0000 @@ -31,11 +31,13 @@ extras_require = { 'flexmock>=0.9.7', 'mock==2.0.0', 'psycopg2>=2.5.1', + 'pg8000>=1.12.4', 'pytz>=2014.2', - 'python-dateutil>=2.2', + 'python-dateutil>=2.6', 'pymysql', 'flake8>=2.4.0', 'isort>=4.2.2', + 'pyodbc', ], 'anyjson': ['anyjson>=0.3.3'], 'babel': ['Babel>=1.3'], @@ -88,10 +90,10 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ]
  39. Download patch tests/primitives/test_country.py

    --- 0.32.21-2/tests/primitives/test_country.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/primitives/test_country.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,3 +1,5 @@ +import operator + import pytest import six @@ -56,6 +58,33 @@ class TestCountry(object): assert Country(u'FI') != u'sv' assert not (Country(u'FI') != u'FI') + @pytest.mark.parametrize( + 'op, code_left, code_right, is_', + [ + (operator.lt, u'ES', u'FI', True), + (operator.lt, u'FI', u'ES', False), + (operator.lt, u'ES', u'ES', False), + + (operator.le, u'ES', u'FI', True), + (operator.le, u'FI', u'ES', False), + (operator.le, u'ES', u'ES', True), + + (operator.ge, u'ES', u'FI', False), + (operator.ge, u'FI', u'ES', True), + (operator.ge, u'ES', u'ES', True), + + (operator.gt, u'ES', u'FI', False), + (operator.gt, u'FI', u'ES', True), + (operator.gt, u'ES', u'ES', False), + ] + ) + def test_ordering(self, op, code_left, code_right, is_): + country_left = Country(code_left) + country_right = Country(code_right) + assert op(country_left, country_right) is is_ + assert op(country_left, code_right) is is_ + assert op(code_left, country_right) is is_ + def test_hash(self): return hash(Country('FI')) == hash('FI')
  40. Download patch sqlalchemy_utils/view.py

    --- 0.32.21-2/sqlalchemy_utils/view.py 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/view.py 2019-12-08 17:37:46.000000000 +0000 @@ -0,0 +1,173 @@ +import sqlalchemy as sa +from sqlalchemy.ext import compiler +from sqlalchemy.schema import DDLElement, PrimaryKeyConstraint + + +class CreateView(DDLElement): + def __init__(self, name, selectable, materialized=False): + self.name = name + self.selectable = selectable + self.materialized = materialized + + +@compiler.compiles(CreateView) +def compile_create_materialized_view(element, compiler, **kw): + return 'CREATE {}VIEW {} AS {}'.format( + 'MATERIALIZED ' if element.materialized else '', + element.name, + compiler.sql_compiler.process(element.selectable, literal_binds=True), + ) + + +class DropView(DDLElement): + def __init__(self, name, materialized=False): + self.name = name + self.materialized = materialized + + +@compiler.compiles(DropView) +def compile_drop_materialized_view(element, compiler, **kw): + return 'DROP {}VIEW IF EXISTS {} CASCADE'.format( + 'MATERIALIZED ' if element.materialized else '', + element.name + ) + + +def create_table_from_selectable( + name, + selectable, + indexes=None, + metadata=None +): + if indexes is None: + indexes = [] + if metadata is None: + metadata = sa.MetaData() + args = [ + sa.Column(c.name, c.type, primary_key=c.primary_key) + for c in selectable.c + ] + indexes + table = sa.Table(name, metadata, *args) + + if not any([c.primary_key for c in selectable.c]): + table.append_constraint( + PrimaryKeyConstraint(*[c.name for c in selectable.c]) + ) + return table + + +def create_materialized_view( + name, + selectable, + metadata, + indexes=None +): + """ Create a view on a given metadata + + :param name: The name of the view to create. + :param selectable: An SQLAlchemy selectable e.g. a select() statement. + :param metadata: + An SQLAlchemy Metadata instance that stores the features of the + database being described. + :param indexes: An optional list of SQLAlchemy Index instances. + + Same as for ``create_view`` except that a ``CREATE MATERIALIZED VIEW`` + statement is emitted instead of a ``CREATE VIEW``. + + """ + table = create_table_from_selectable( + name=name, + selectable=selectable, + indexes=indexes, + metadata=None + ) + + sa.event.listen( + metadata, + 'after_create', + CreateView(name, selectable, materialized=True) + ) + + @sa.event.listens_for(metadata, 'after_create') + def create_indexes(target, connection, **kw): + for idx in table.indexes: + idx.create(connection) + + sa.event.listen( + metadata, + 'before_drop', + DropView(name, materialized=True) + ) + return table + + +def create_view( + name, + selectable, + metadata +): + """ Create a view on a given metadata + + :param name: The name of the view to create. + :param selectable: An SQLAlchemy selectable e.g. a select() statement. + :param metadata: + An SQLAlchemy Metadata instance that stores the features of the + database being described. + + The process for creating a view is similar to the standard way that a + table is constructed, except that a selectable is provided instead of + a set of columns. The view is created once a ``CREATE`` statement is + executed against the supplied metadata (e.g. ``metadata.create_all(..)``), + and dropped when a ``DROP`` is executed against the metadata. + + To create a view that performs basic filtering on a table. :: + + metadata = MetaData() + users = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + Column('premium_user', Boolean, default=False), + ) + + premium_members = select([users]).where(users.c.premium_user == True) + create_view('premium_users', premium_members, metadata) + + metadata.create_all(engine) # View is created at this point + + """ + table = create_table_from_selectable( + name=name, + selectable=selectable, + metadata=None + ) + + sa.event.listen(metadata, 'after_create', CreateView(name, selectable)) + + @sa.event.listens_for(metadata, 'after_create') + def create_indexes(target, connection, **kw): + for idx in table.indexes: + idx.create(connection) + + sa.event.listen(metadata, 'before_drop', DropView(name)) + return table + + +def refresh_materialized_view(session, name, concurrently=False): + """ Refreshes an already existing materialized view + + :param session: An SQLAlchemy Session instance. + :param name: The name of the materialized view to refresh. + :param concurrently: + Optional flag that causes the ``CONCURRENTLY`` parameter + to be specified when the materialized view is refreshed. + """ + # Since session.execute() bypasses autoflush, we must manually flush in + # order to include newly-created/modified objects in the refresh. + session.flush() + session.execute( + 'REFRESH MATERIALIZED VIEW {}{}'.format( + 'CONCURRENTLY ' if concurrently else '', + name + ) + )
  41. Download patch sqlalchemy_utils/primitives/country.py

    --- 0.32.21-2/sqlalchemy_utils/primitives/country.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/primitives/country.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,9 +1,12 @@ +from functools import total_ordering + import six from .. import i18n from ..utils import str_coercible +@total_ordering @str_coercible class Country(object): """ @@ -25,7 +28,8 @@ class Country(object): Country(Country('FI')).code # 'FI' - Country always validates the given code. + Country always validates the given code if you use at least the optional + dependency list 'babel', otherwise no validation are performed. :: @@ -76,6 +80,9 @@ class Country(object): raise ValueError( 'Could not convert string to country code: {0}'.format(code) ) + except AttributeError: + # As babel is optional, we may raise an AttributeError accessing it + pass def __eq__(self, other): if isinstance(other, Country): @@ -91,6 +98,13 @@ class Country(object): def __ne__(self, other): return not (self == other) + def __lt__(self, other): + if isinstance(other, Country): + return self.code < other.code + elif isinstance(other, six.string_types): + return self.code < other + return NotImplemented + def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.code)
  42. Download patch tests/types/test_password.py

    --- 0.32.21-2/tests/types/test_password.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/types/test_password.py 2019-12-08 17:37:46.000000000 +0000 @@ -109,7 +109,7 @@ class TestPasswordType(object): from passlib.hash import md5_crypt obj = User() - obj.password = Password(md5_crypt.encrypt('b')) + obj.password = Password(md5_crypt.hash('b')) assert obj.password.hash.decode('utf8').startswith('$1$') assert obj.password == 'b' @@ -139,10 +139,10 @@ class TestPasswordType(object): from passlib.hash import md5_crypt obj = User() - obj.password = Password(md5_crypt.encrypt('b')) + obj.password = Password(md5_crypt.hash('b')) other = User() - other.password = Password(md5_crypt.encrypt('b')) + other.password = Password(md5_crypt.hash('b')) # Not sure what to assert here; the test raised an error before. assert obj.password != other.password @@ -203,7 +203,7 @@ class TestPasswordType(object): from passlib.hash import md5_crypt obj = User() - obj.password = Password(md5_crypt.encrypt('b')) + obj.password = Password(md5_crypt.hash('b')) session.add(obj) session.commit()
  43. Download patch sqlalchemy_utils/types/range.py

    --- 0.32.21-2/sqlalchemy_utils/types/range.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/range.py 2019-12-08 17:37:46.000000000 +0000 @@ -134,7 +134,10 @@ than 500. .. _intervals: https://github.com/kvesteri/intervals """ -from collections import Iterable +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable from datetime import timedelta import six @@ -143,6 +146,7 @@ from sqlalchemy import types from sqlalchemy.dialects.postgresql import ( DATERANGE, INT4RANGE, + INT8RANGE, NUMRANGE, TSRANGE ) @@ -360,6 +364,58 @@ class IntRangeType(RangeType): self.interval_class = intervals.IntInterval +class Int8RangeType(RangeType): + """ + Int8RangeType provides way for saving ranges of 8-byte integers into + database. On PostgreSQL this type maps to native INT8RANGE type while on + other drivers this maps to simple string column. + + Example:: + + + from sqlalchemy_utils import IntRangeType + + + class Event(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, autoincrement=True) + name = sa.Column(sa.Unicode(255)) + estimated_number_of_persons = sa.Column(Int8RangeType) + + + party = Event(name=u'party') + + # we estimate the party to contain minium of 10 persons and at max + # 100 persons + party.estimated_number_of_persons = [10, 100] + + print party.estimated_number_of_persons + # '10-100' + + + Int8RangeType returns the values as IntInterval objects. These objects + support many arithmetic operators:: + + + meeting = Event(name=u'meeting') + + meeting.estimated_number_of_persons = [20, 40] + + total = ( + meeting.estimated_number_of_persons + + party.estimated_number_of_persons + ) + print total + # '30-140' + """ + impl = INT8RANGE + comparator_factory = IntRangeComparator + + def __init__(self, *args, **kwargs): + super(Int8RangeType, self).__init__(*args, **kwargs) + self.interval_class = intervals.IntInterval + + class DateRangeType(RangeType): """ DateRangeType provides way for saving ranges of dates into database. On
  44. Download patch docs/internationalization.rst

    --- 0.32.21-2/docs/internationalization.rst 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/docs/internationalization.rst 2019-12-08 17:37:46.000000000 +0000 @@ -88,7 +88,7 @@ fetch a the locale returned by `default_ Translation hybrids can also be used as expressions. :: - session.query(Article).filter(Article.name['en'] == 'Some article') + session.query(Article).filter(Article.name_translations['en'] == 'Some article') By default if no value is found for either current or default locale the @@ -120,7 +120,9 @@ Dynamic locales --------------- Sometimes locales need to be dynamic. The following example illustrates how to setup -dynamic locales. +dynamic locales. You can pass a callable of either 0, 1 or 2 args as a constructor parameter for TranslationHybrid. + +The first argument should be the associated object and second parameter the name of the translations attribute. :: @@ -142,12 +144,24 @@ dynamic locales. article = Article(name_translations={'en': 'Some article'}) + article.locale = 'en' session.add(article) session.commit() article.name # Some article (even if current locale is other than 'en') +The locales can also be attribute dependent so you can set up translation hybrid in a way that +it is guaranteed to return a translation. + +:: + + translation_hybrid.default_locale = lambda obj, attr: sorted(getattr(obj, attr).keys())[0] + + + article.name # Some article + + .. _SQLAlchemy-i18n: https://github.com/kvesteri/sqlalchemy-i18n
  45. Download patch sqlalchemy_utils/utils.py

    --- 0.32.21-2/sqlalchemy_utils/utils.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/utils.py 2019-12-08 17:37:46.000000000 +0000 @@ -1,8 +1,12 @@ import sys -from collections import Iterable import six +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable + def str_coercible(cls): if sys.version_info[0] >= 3: # Python 3
  46. Download patch tests/types/test_arrow.py

    --- 0.32.21-2/tests/types/test_arrow.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/types/test_arrow.py 2019-12-08 17:37:46.000000000 +0000 @@ -39,7 +39,7 @@ class TestArrowDateTimeType(object): def test_string_coercion(self, Article): article = Article( - created_at='1367900664' + created_at='2013-01-01' ) assert article.created_at.year == 2013
  47. Download patch tests/test_expressions.py

    --- 0.32.21-2/tests/test_expressions.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/test_expressions.py 2019-12-08 17:37:46.000000000 +0000 @@ -3,7 +3,6 @@ import sqlalchemy as sa from sqlalchemy.dialects import postgresql from sqlalchemy_utils import Asterisk, row_to_json -from sqlalchemy_utils.expressions import explain, explain_analyze @pytest.fixture @@ -27,73 +26,6 @@ def Article(Base): return Article -@pytest.mark.usefixtures('postgresql_dsn') -class TestExplain(object): - - def test_render_explain(self, session, assert_startswith, Article): - assert_startswith( - explain(session.query(Article)), - 'EXPLAIN SELECT' - ) - - def test_render_explain_with_analyze( - self, - session, - assert_startswith, - Article - ): - assert_startswith( - explain(session.query(Article), analyze=True), - 'EXPLAIN (ANALYZE true) SELECT' - ) - - def test_with_string_as_stmt_param(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article'), - 'EXPLAIN SELECT' - ) - - def test_format(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article', format='json'), - 'EXPLAIN (FORMAT json) SELECT' - ) - - def test_timing(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article', analyze=True, timing=False), - 'EXPLAIN (ANALYZE true, TIMING false) SELECT' - ) - - def test_verbose(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article', verbose=True), - 'EXPLAIN (VERBOSE true) SELECT' - ) - - def test_buffers(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article', analyze=True, buffers=True), - 'EXPLAIN (ANALYZE true, BUFFERS true) SELECT' - ) - - def test_costs(self, assert_startswith): - assert_startswith( - explain('SELECT 1 FROM article', costs=False), - 'EXPLAIN (COSTS false) SELECT' - ) - - -class TestExplainAnalyze(object): - def test_render_explain_analyze(self, session, Article): - assert str( - explain_analyze(session.query(Article)) - .compile( - dialect=postgresql.dialect() - ) - ).startswith('EXPLAIN (ANALYZE true) SELECT') - - class TestAsterisk(object): def test_with_table_object(self): Base = sa.ext.declarative.declarative_base() @@ -132,35 +64,3 @@ class TestRowToJson(object): sa.func.row_to_json(sa.text('article.*')).type, postgresql.JSON ) - - -class TestArrayAgg(object): - def test_compiler_with_default_dialect(self): - assert str(sa.func.array_agg(sa.text('u.name'))) == ( - 'array_agg(u.name)' - ) - - def test_compiler_with_postgresql(self): - assert str(sa.func.array_agg(sa.text('u.name')).compile( - dialect=postgresql.dialect() - )) == 'array_agg(u.name)' - - def test_type(self): - assert isinstance( - sa.func.array_agg(sa.text('u.name')).type, - postgresql.ARRAY - ) - - def test_array_agg_with_default(self): - Base = sa.ext.declarative.declarative_base() - - class Article(Base): - __tablename__ = 'article' - id = sa.Column(sa.Integer, primary_key=True) - - assert str(sa.func.array_agg(Article.id, [1]).compile( - dialect=postgresql.dialect() - )) == ( - 'coalesce(array_agg(article.id), CAST(ARRAY[%(param_1)s]' - ' AS INTEGER[]))' - )
  48. Download patch debian/gbp.conf

    --- 0.32.21-2/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ 0.36.0-0ubuntu1/debian/gbp.conf 2019-12-19 15:13:47.000000000 +0000 @@ -0,0 +1,7 @@ +[DEFAULT] +debian-branch = master +upstream-tag = %(version)s +pristine-tar = True + +[buildpackage] +export-dir = ../build-area
  49. Download patch tests/primitives/test_ltree.py

    --- 0.32.21-2/tests/primitives/test_ltree.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/tests/primitives/test_ltree.py 2019-12-08 17:37:46.000000000 +0000 @@ -141,6 +141,37 @@ class TestLtree(object): def test_contains(self, path, other, result): assert (other in Ltree(path)) == result + @pytest.mark.parametrize( + ('path', 'other', 'result'), + ( + ('1', '1.2.3', True), + ('1.2', '1.2.3', True), + ('1.2.3', '1.2.3', True), + ('1.2.3', '1', False), + ('1.2.3', '1.2', False), + ('1', '1', True), + ('1', '2', False), + ) + ) + def test_ancestor_of(self, path, other, result): + assert Ltree(path).ancestor_of(other) == result + + @pytest.mark.parametrize( + ('path', 'other', 'result'), + ( + ('1', '1.2.3', False), + ('1.2', '1.2.3', False), + ('1.2', '1.2.3', False), + ('1.2.3', '1', True), + ('1.2.3', '1.2', True), + ('1.2.3', '1.2.3', True), + ('1', '1', True), + ('1', '2', False), + ) + ) + def test_descendant_of(self, path, other, result): + assert Ltree(path).descendant_of(other) == result + def test_getitem_with_other_than_slice_or_in(self): with pytest.raises(TypeError): Ltree('1.2')['something']
  50. Download patch sqlalchemy_utils/__init__.py

    --- 0.32.21-2/sqlalchemy_utils/__init__.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/__init__.py 2019-12-08 17:37:46.000000000 +0000 @@ -9,7 +9,6 @@ from .asserts import ( # noqa from .exceptions import ImproperlyConfigured # noqa from .expressions import Asterisk, row_to_json # noqa from .functions import ( # noqa - analyze, cast_if, create_database, create_mock_engine, @@ -73,6 +72,7 @@ from .types import ( # noqa EncryptedType, instrumented_list, InstrumentedList, + Int8RangeType, IntRangeType, IPAddressType, JSONType, @@ -94,5 +94,10 @@ from .types import ( # noqa UUIDType, WeekDaysType ) +from .view import ( # noqa + create_materialized_view, + create_view, + refresh_materialized_view +) -__version__ = '0.32.21' +__version__ = '0.36.0'
  51. Download patch tests/types/test_encrypted.py
  52. Download patch sqlalchemy_utils/types/choice.py

    --- 0.32.21-2/sqlalchemy_utils/types/choice.py 2017-11-11 17:52:23.000000000 +0000 +++ 0.36.0-0ubuntu1/sqlalchemy_utils/types/choice.py 2019-12-08 17:37:46.000000000 +0000 @@ -29,6 +29,9 @@ class Choice(object): def __unicode__(self): return six.text_type(self.value) + def __str__(self): + return six.ensure_str(self.__unicode__()) + def __repr__(self): return 'Choice(code={code}, value={value})'.format( code=self.code, @@ -65,7 +68,7 @@ class ChoiceType(types.TypeDecorator, Sc user = User(type=u'admin') - user.type # Choice(type='admin', value=u'Admin') + user.type # Choice(code='admin', value=u'Admin') Or:: @@ -109,7 +112,7 @@ class ChoiceType(types.TypeDecorator, Sc user = User(type=u'admin') - user.type # Choice(type='admin', value=u'Admin') + user.type # Choice(code='admin', value=u'Admin') print user.type # u'Admin'

Debian ( Changelog | PTS | Bugs ) Ubuntu ( Changelog | txt | LP | Bugs ) | Diff from Ubuntu

Source: sqlalchemy

sqlalchemy (1.3.11+ds1-1ubuntu1) focal; urgency=medium [Dimitri John Ledkov] * Drop recommending python2 package from the docs package. -- Corey Bryant <corey.bryant@canonical.com> Tue, 10 Dec 2019 09:15:13 -0500

Modifications :
  1. Download patch debian/control

    --- 1.3.11+ds1-1/debian/control 2019-11-07 20:40:13.000000000 +0000 +++ 1.3.11+ds1-1ubuntu1/debian/control 2019-12-10 14:15:13.000000000 +0000 @@ -1,7 +1,8 @@ Source: sqlalchemy Section: python Priority: optional -Maintainer: Piotr O┼╝arowski <piotr@debian.org> +Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> +XSBC-Original-Maintainer: Piotr O┼╝arowski <piotr@debian.org> Uploaders: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> Build-Depends: debhelper-compat (= 9), dh-python, python-all-dev (>= 2.6.5-2~), python3-all-dev (>= 3.1.2-8~), @@ -72,7 +73,6 @@ Package: python-sqlalchemy-doc Section: doc Architecture: all Depends: ${misc:Depends}, libjs-jquery, libjs-underscore -Recommends: python-sqlalchemy Conflicts: python-sqlalchemy (<= 0.3.0-1) Description: documentation for the SQLAlchemy Python library SQLAlchemy is an SQL database abstraction library for Python.
  1. python-sqlalchemy-utils
  2. sqlalchemy