From edcd854943ee57c48d7f8a5c82665666a508cbe5 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 19:57:34 +0000 Subject: [PATCH 01/33] Use Ubuntu Xenial and Python 3.7 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5e78b69..a850b07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ sudo: false + language: python +python: 3.7 + +dist: xenial services: - mysql @@ -28,7 +32,6 @@ matrix: - stage: test python: 3.7 env: TOX_ENV=py37 - dist: xenial - stage: deploy script: skip From 43cd6156ca436da8baf5fae4814219c86b06da4e Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 20:08:01 +0000 Subject: [PATCH 02/33] Add PostgreSQL v10 to Travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index a850b07..1ac2ba7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,10 @@ dist: xenial services: - mysql + - postgresql + +addons: + postgresql: "10" install: - pip install tox From b759b119f6d080ca282759d0b8a88af165133844 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 20:44:46 +0000 Subject: [PATCH 03/33] Remove Travis addons: GitHub check not going green --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ac2ba7..9972798 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,6 @@ services: - mysql - postgresql -addons: - postgresql: "10" - install: - pip install tox From cbcfa2a9b827bade0de59b7e4a16480a27d2c643 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 20:54:31 +0000 Subject: [PATCH 04/33] Travis CI: use addons to install postgresql --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9972798..d74a5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,9 @@ dist: xenial services: - mysql - - postgresql + +addons: + postgresql: "9.6" install: - pip install tox From 343080ed46c65d71b7b4464e311eba6a083edb69 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 21:02:06 +0000 Subject: [PATCH 05/33] Install psycopg2 as part of the tests --- setup.py | 3 +++ tox.ini | 1 + 2 files changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 43edb08..aaa7e65 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,9 @@ 'mysql': [ 'mysql-connector-python-rf==2.2.2', ], + 'postgresql': [ + 'psycopg2==2.7.7' + ], 'python2': [ "funcsigs>=1.0.2" ] diff --git a/tox.ini b/tox.ini index 2103bc7..77b829b 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ usedevelop = true extras = dev mysql + postgresql deps = py27: funcsigs commands = From c2e55779886f56c8da0d860cc61b96d8a7f8d73a Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 21:27:43 +0000 Subject: [PATCH 06/33] Run tests also using PostgreSQL --- test/conftest.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 02c368c..8ae80fd 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -10,6 +10,7 @@ SQLITE_TEST_DB_URI = 'SQLITE_TEST_DB_URI' MYSQL_TEST_DB_URI = 'MYSQL_TEST_DB_URI' +POSTGRESQL_TEST_DB_URI = 'POSTGRESQL_TEST_DB_URI' def pytest_addoption(parser): @@ -39,12 +40,30 @@ def pytest_addoption(parser): ) ) + parser.addoption( + '--postgresql-test-db-uri', + action='store', + dest=POSTGRESQL_TEST_DB_URI, + default=( + 'postgresql+psycopg2://postgres:@localhost:5432' + '/test_sqlalchemy_filters?client_encoding=utf8' + ), + help=( + 'DB uri for testing (e.g. ' + '"postgresql+psycopg2://username:password@localhost:5432' + '/test_sqlalchemy_filters?client_encoding=utf8")' + ) + ) + @pytest.fixture(scope='session') def config(request): return { SQLITE_TEST_DB_URI: request.config.getoption(SQLITE_TEST_DB_URI), MYSQL_TEST_DB_URI: request.config.getoption(MYSQL_TEST_DB_URI), + POSTGRESQL_TEST_DB_URI: request.config.getoption( + POSTGRESQL_TEST_DB_URI + ), } @@ -60,6 +79,13 @@ def test_db_keys(): else: test_db_uris.append(MYSQL_TEST_DB_URI) + try: + import psycopg2 # noqa: F401 + except ImportError: + pass + else: + test_db_uris.append(POSTGRESQL_TEST_DB_URI) + return test_db_uris @@ -69,9 +95,17 @@ def db_uri(request, config): @pytest.fixture(scope='session') -def connection(db_uri): +def db_engine_options(db_uri): + return dict( + client_encoding='utf8', + connect_args={'client_encoding': 'utf8'} + ) + + +@pytest.fixture(scope='session') +def connection(db_uri, db_engine_options): create_db(db_uri) - engine = create_engine(db_uri) + engine = create_engine(db_uri, **db_engine_options) Base.metadata.create_all(engine) connection = engine.connect() Base.metadata.bind = engine From c1c4de38f986bef6a6772084ef5f06cf19494ef7 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 21 Feb 2019 21:35:02 +0000 Subject: [PATCH 07/33] Only return DB engine options for PostgreSQL --- test/conftest.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 8ae80fd..2b190a0 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -12,6 +12,8 @@ MYSQL_TEST_DB_URI = 'MYSQL_TEST_DB_URI' POSTGRESQL_TEST_DB_URI = 'POSTGRESQL_TEST_DB_URI' +POSTGRESQL_DB = 'postgresql' + def pytest_addoption(parser): parser.addoption( @@ -96,10 +98,12 @@ def db_uri(request, config): @pytest.fixture(scope='session') def db_engine_options(db_uri): - return dict( - client_encoding='utf8', - connect_args={'client_encoding': 'utf8'} - ) + if POSTGRESQL_DB in db_uri: + return dict( + client_encoding='utf8', + connect_args={'client_encoding': 'utf8'} + ) + return {} @pytest.fixture(scope='session') From e1682c2e0850990344df1ae405a3b497c67fff86 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 18:58:03 +0000 Subject: [PATCH 08/33] Exclude NULL sorting and non explicit sorting from tests --- test/interface/test_sorting.py | 110 +++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 41 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index c68e98d..1c08c3b 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -12,7 +12,7 @@ @pytest.fixture -def multiple_foos_inserted(session, multiple_bars_inserted): +def multiple_foos_inserted(session, multiple_bars_with_no_nulls_inserted): foo_1 = Foo(id=1, bar_id=1, name='name_1', count=1) foo_2 = Foo(id=2, bar_id=2, name='name_2', count=1) foo_3 = Foo(id=3, bar_id=3, name='name_1', count=1) @@ -21,25 +21,21 @@ def multiple_foos_inserted(session, multiple_bars_inserted): foo_6 = Foo(id=6, bar_id=6, name='name_4', count=2) foo_7 = Foo(id=7, bar_id=7, name='name_1', count=2) foo_8 = Foo(id=8, bar_id=8, name='name_5', count=2) - session.add_all( - [foo_1, foo_2, foo_3, foo_4, foo_5, foo_6, foo_7, foo_8] - ) + session.add_all([foo_1, foo_2, foo_3, foo_4, foo_5, foo_6, foo_7, foo_8]) session.commit() @pytest.fixture -def multiple_bars_inserted(session): +def multiple_bars_with_no_nulls_inserted(session): bar_1 = Bar(id=1, name='name_1', count=5) bar_2 = Bar(id=2, name='name_2', count=10) - bar_3 = Bar(id=3, name='name_1', count=None) + bar_3 = Bar(id=3, name='name_1', count=3) bar_4 = Bar(id=4, name='name_4', count=12) bar_5 = Bar(id=5, name='name_1', count=2) bar_6 = Bar(id=6, name='name_4', count=15) bar_7 = Bar(id=7, name='name_1', count=2) bar_8 = Bar(id=8, name='name_5', count=1) - session.add_all( - [bar_1, bar_2, bar_3, bar_4, bar_5, bar_6, bar_7, bar_8] - ) + session.add_all([bar_1, bar_2, bar_3, bar_4, bar_5, bar_6, bar_7, bar_8]) session.commit() @@ -109,7 +105,21 @@ def test_invalid_direction(self, session): class TestSortApplied(object): - @pytest.mark.usefixtures('multiple_bars_inserted') + """Tests that the results are sorted according to the provided + filters. + + Do NOT test how any specific DBMS sorts the results for rows + that have the same value in the field that is being sorted since + this is not consistent accross DBMS. + + Also, sorting on fields with `None` values is NOT tested since + different DBMS behave differently when sorting `NULL` values. SQL + defines that `NULL` values should be placed together when sorting, + but it does not specify whether they should be placed first or last + in the result. + """ + + @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_single_sort_field_asc(self, session): query = session.query(Bar) order_by = [{'field': 'name', 'direction': 'asc'}] @@ -117,17 +127,23 @@ def test_single_sort_field_asc(self, session): sorted_query = apply_sort(query, order_by) result = sorted_query.all() + result_same_name_1 = [ + result[0].id, result[1].id, result[2].id, result[3].id + ] + result_same_name_4 = [result[5].id, result[6].id] + assert len(result) == 8 - assert result[0].id == 1 - assert result[1].id == 3 - assert result[2].id == 5 - assert result[3].id == 7 + + assert 1 in result_same_name_1 + assert 3 in result_same_name_1 + assert 5 in result_same_name_1 + assert 7 in result_same_name_1 assert result[4].id == 2 - assert result[5].id == 4 - assert result[6].id == 6 + assert 4 in result_same_name_4 + assert 6 in result_same_name_4 assert result[7].id == 8 - @pytest.mark.usefixtures('multiple_bars_inserted') + @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_single_sort_field_desc(self, session): query = session.query(Bar) order_by = [{'field': 'name', 'direction': 'desc'}] @@ -135,17 +151,23 @@ def test_single_sort_field_desc(self, session): sorted_query = apply_sort(query, order_by) result = sorted_query.all() + result_same_name_4 = [result[1].id, result[2].id] + result_same_name_1 = [ + result[4].id, result[5].id, result[6].id, result[7].id + ] + assert len(result) == 8 + assert result[0].id == 8 - assert result[1].id == 4 - assert result[2].id == 6 + assert 4 in result_same_name_4 + assert 6 in result_same_name_4 assert result[3].id == 2 - assert result[4].id == 1 - assert result[5].id == 3 - assert result[6].id == 5 - assert result[7].id == 7 + assert 1 in result_same_name_1 + assert 3 in result_same_name_1 + assert 5 in result_same_name_1 + assert 7 in result_same_name_1 - @pytest.mark.usefixtures('multiple_bars_inserted') + @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_multiple_sort_fields(self, session): query = session.query(Bar) order_by = [ @@ -159,9 +181,9 @@ def test_multiple_sort_fields(self, session): assert len(result) == 8 assert result[0].id == 1 - assert result[1].id == 7 - assert result[2].id == 5 - assert result[3].id == 3 + assert result[1].id == 3 + assert result[2].id == 7 + assert result[3].id == 5 assert result[4].id == 2 assert result[5].id == 6 assert result[6].id == 4 @@ -169,13 +191,13 @@ def test_multiple_sort_fields(self, session): def test_multiple_models(self, session): - bar_1 = Bar(id=1, name='name_1', count=5) + bar_1 = Bar(id=1, name='name_1', count=15) bar_2 = Bar(id=2, name='name_2', count=10) - bar_3 = Bar(id=3, name='name_1', count=None) - bar_4 = Bar(id=4, name='name_1', count=12) + bar_3 = Bar(id=3, name='name_1', count=20) + bar_4 = Bar(id=4, name='name_1', count=10) qux_1 = Qux( - id=1, name='name_1', count=5, + id=1, name='name_1', count=15, created_at=datetime.date(2016, 7, 12), execution_time=datetime.datetime(2016, 7, 12, 1, 5, 9) ) @@ -185,11 +207,11 @@ def test_multiple_models(self, session): execution_time=datetime.datetime(2016, 7, 13, 2, 5, 9) ) qux_3 = Qux( - id=3, name='name_1', count=None, + id=3, name='name_1', count=10, created_at=None, execution_time=None ) qux_4 = Qux( - id=4, name='name_1', count=15, + id=4, name='name_1', count=20, created_at=datetime.date(2016, 7, 14), execution_time=datetime.datetime(2016, 7, 14, 3, 5, 9) ) @@ -202,7 +224,7 @@ def test_multiple_models(self, session): query = session.query(Bar).join(Qux, Bar.id == Qux.id) order_by = [ {'model': 'Bar', 'field': 'name', 'direction': 'asc'}, - {'model': 'Qux', 'field': 'count', 'direction': 'asc'} + {'model': 'Qux', 'field': 'count', 'direction': 'asc'}, ] sorted_query = apply_sort(query, order_by) @@ -214,7 +236,7 @@ def test_multiple_models(self, session): assert result[2].id == 4 assert result[3].id == 2 - @pytest.mark.usefixtures('multiple_bars_inserted') + @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): query = session.query(Bar) sort_spec = {'field': 'name', 'direction': 'desc'} @@ -222,15 +244,21 @@ def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): sorted_query = apply_sort(query, sort_spec) result = sorted_query.all() + result_same_name_4 = [result[1].id, result[2].id] + result_same_name_1 = [ + result[4].id, result[5].id, result[6].id, result[7].id + ] + assert len(result) == 8 + assert result[0].id == 8 - assert result[1].id == 4 - assert result[2].id == 6 + assert 4 in result_same_name_4 + assert 6 in result_same_name_4 assert result[3].id == 2 - assert result[4].id == 1 - assert result[5].id == 3 - assert result[6].id == 5 - assert result[7].id == 7 + assert 1 in result_same_name_1 + assert 3 in result_same_name_1 + assert 5 in result_same_name_1 + assert 7 in result_same_name_1 class TestAutoJoin: From 79e991b5111f30c0eb1624c35bc31d4da33e9ba9 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:07:08 +0000 Subject: [PATCH 09/33] Add test fixture to know whether test are running on PostgreSQL --- test/conftest.py | 9 ++++++++- test/interface/test_sorting.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 2b190a0..8916eb5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -97,8 +97,15 @@ def db_uri(request, config): @pytest.fixture(scope='session') -def db_engine_options(db_uri): +def is_postgresql(db_uri): if POSTGRESQL_DB in db_uri: + return True + return False + + +@pytest.fixture(scope='session') +def db_engine_options(db_uri, is_postgresql): + if is_postgresql: return dict( client_encoding='utf8', connect_args={'client_encoding': 'utf8'} diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 1c08c3b..ad36532 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -110,7 +110,7 @@ class TestSortApplied(object): Do NOT test how any specific DBMS sorts the results for rows that have the same value in the field that is being sorted since - this is not consistent accross DBMS. + this is not consistent across DBMS. Also, sorting on fields with `None` values is NOT tested since different DBMS behave differently when sorting `NULL` values. SQL From 29eac10db169bd59736247b3a2b26867e4c0f2fc Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:08:31 +0000 Subject: [PATCH 10/33] Add Makefile targets to run RDMS docker containers for testing --- Makefile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Makefile b/Makefile index 6277c98..aa1a3bb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ .PHONY: test +POSTGRES_VERSION?=9.6.9 +MYSQL_VERSION?=5.7 + rst-lint: rst-lint README.rst @@ -14,3 +17,19 @@ test: flake8 coverage: flake8 rst-lint coverage run --source sqlalchemy_filters -m pytest test $(ARGS) coverage report -m --fail-under 100 + + +# Docker test containers + +docker-mysql-run: + docker run -d --name mysql-postgres-sqlalchemy-filters -p 3306:3306 \ + -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ + mysql:$(MYSQL_VERSION) + +docker-postgres-run: + docker run -d --name postgres-sqlalchemy-filters -p 5432:5432 \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD= \ + -e POSTGRES_DB=test_sqlalchemy_filters \ + -e POSTGRES_INITDB_ARGS="--encoding=UTF8 --lc-collate=en_US.utf8 --lc-ctype=en_US.utf8" \ + postgres:$(POSTGRES_VERSION) From 4072fd049f5880cc9b6d30be99abf0117462cfb0 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:16:06 +0000 Subject: [PATCH 11/33] Improve documentation --- README.rst | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index e62dda0..1e9711a 100644 --- a/README.rst +++ b/README.rst @@ -317,19 +317,37 @@ The `model` key is optional if the original query being sorted only applies to o Running tests ------------- -There are some Makefile targets that can be used to run the tests. A -test database will be created, used during the tests and destroyed -afterwards. - -The default configuration uses both SQLite and MySQL (if the driver is -installed) to run the tests, with the following URIs: +The default configuration uses **SQLite**, **MySQL** (if the driver is +installed, which is the case when `tox` is used) and **PostgreSQL** (if +the driver is installed, which is the case when `tox` is used) to run +the tests, with the following URIs: .. code-block:: shell sqlite+pysqlite:///test_sqlalchemy_filters.db mysql+mysqlconnector://root:@localhost:3306/test_sqlalchemy_filters + postgresql+psycopg2://postgres:@localhost:5432/test_sqlalchemy_filters?client_encoding=utf8' + +A test database will be created, used during the tests and destroyed +afterwards for each RDMS configured. + +There are Makefile targets to run docker containers locally for both +**MySQL** and **PostgreSQL**, using the default ports and configuration: + +.. code-block:: shell + + $ make docker-mysql-run + $ make docker-postgres-run -Example of usage: +To run the tests locally: + +.. code-block:: shell + + $ # Create/activate a virtual environment + $ pip install tox + $ tox + +There are some other Makefile targets that can be used to run the tests: .. code-block:: shell @@ -345,6 +363,17 @@ Example of usage: $ ARGS='--sqlite-test-db-uri sqlite+pysqlite:///test_sqlalchemy_filters.db' make coverage + +Database management systems +--------------------------- + +The following database management systems are supported (with test coverage): + +- SQLite +- MySQL +- PostgreSQL + + Python 2 -------- From 9ce72b439dc78a8c9c40ade4b072c65d88ed2c38 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:19:23 +0000 Subject: [PATCH 12/33] Improve documentation --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1e9711a..88c7ab2 100644 --- a/README.rst +++ b/README.rst @@ -377,7 +377,7 @@ The following database management systems are supported (with test coverage): Python 2 -------- -There is no active support for python 2, however it is compatiable as of February 2019, if you install funcsigs. +There is no active support for python 2, however it is compatiable as of February 2019, if you install `funcsigs`. License From 231d17c97e3ab53c2db792c664ce0de3dd16c2da Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:21:09 +0000 Subject: [PATCH 13/33] Improve documentation --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 88c7ab2..e37c1ce 100644 --- a/README.rst +++ b/README.rst @@ -377,7 +377,7 @@ The following database management systems are supported (with test coverage): Python 2 -------- -There is no active support for python 2, however it is compatiable as of February 2019, if you install `funcsigs`. +There is no active support for python 2, however it is compatiable as of February 2019, if you install ``funcsigs``. License From 26f70814f8edd662d4fdad75e691a0925e355c0e Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 19:33:40 +0000 Subject: [PATCH 14/33] Comments to keep DB versions synchronized --- .travis.yml | 1 + Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d74a5db..dddcece 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: 3.7 dist: xenial +# Keep DB versions synchronized with the ones in the Makefile for local testing services: - mysql diff --git a/Makefile b/Makefile index aa1a3bb..78d8000 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ .PHONY: test +# Versions used by Travis CI. Keep them synchronized. POSTGRES_VERSION?=9.6.9 MYSQL_VERSION?=5.7 From 33874300243027577a7aac51d14eb5c6e651423d Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 20:09:43 +0000 Subject: [PATCH 15/33] Do not use Docker patch versions to be in sync with Travis CI --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 78d8000..72dea60 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: test # Versions used by Travis CI. Keep them synchronized. -POSTGRES_VERSION?=9.6.9 +POSTGRES_VERSION?=9.6 MYSQL_VERSION?=5.7 From a365970f89df6f4affbf7735e3f73c3eb1c01038 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sat, 23 Feb 2019 20:17:23 +0000 Subject: [PATCH 16/33] Update documentation --- README.rst | 4 ++-- test/interface/test_sorting.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index e37c1ce..e159337 100644 --- a/README.rst +++ b/README.rst @@ -329,7 +329,7 @@ the tests, with the following URIs: postgresql+psycopg2://postgres:@localhost:5432/test_sqlalchemy_filters?client_encoding=utf8' A test database will be created, used during the tests and destroyed -afterwards for each RDMS configured. +afterwards for each RDBMS configured. There are Makefile targets to run docker containers locally for both **MySQL** and **PostgreSQL**, using the default ports and configuration: @@ -367,7 +367,7 @@ There are some other Makefile targets that can be used to run the tests: Database management systems --------------------------- -The following database management systems are supported (with test coverage): +The following RDBMS are supported (tested): - SQLite - MySQL diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index ad36532..78faf30 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -108,12 +108,12 @@ class TestSortApplied(object): """Tests that the results are sorted according to the provided filters. - Do NOT test how any specific DBMS sorts the results for rows + Do NOT test how any specific RDBMS sorts the results for rows that have the same value in the field that is being sorted since - this is not consistent across DBMS. + this is not consistent across RDBMS. Also, sorting on fields with `None` values is NOT tested since - different DBMS behave differently when sorting `NULL` values. SQL + different RDBMS behave differently when sorting `NULL` values. SQL defines that `NULL` values should be placed together when sorting, but it does not specify whether they should be placed first or last in the result. From 69ac461d41351795a8e722a0ae1c4e899a0bd34f Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sun, 24 Feb 2019 13:31:44 +0000 Subject: [PATCH 17/33] Improve documentation --- test/interface/test_sorting.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 78faf30..2f7b0e2 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -105,18 +105,18 @@ def test_invalid_direction(self, session): class TestSortApplied(object): - """Tests that the results are sorted according to the provided + """Tests that results are sorted only according to the provided filters. - Do NOT test how any specific RDBMS sorts the results for rows - that have the same value in the field that is being sorted since - this is not consistent across RDBMS. + Exclude nonexplicit sorting from tests: do NOT test how different + RDBMS sort rows with the same values being ordered since this is not + consistent across RDBMS. - Also, sorting on fields with `None` values is NOT tested since - different RDBMS behave differently when sorting `NULL` values. SQL - defines that `NULL` values should be placed together when sorting, - but it does not specify whether they should be placed first or last - in the result. + Exclude `NULL` sorting from tests: sorting fields containing `NULL` + values is NOT tested since different RDBMS behave differently when + sorting `NULL` values. SQL defines that `NULL` values should be + placed together when sorting, but it does not specify whether they + should be placed first or last in the result. """ @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') @@ -133,7 +133,6 @@ def test_single_sort_field_asc(self, session): result_same_name_4 = [result[5].id, result[6].id] assert len(result) == 8 - assert 1 in result_same_name_1 assert 3 in result_same_name_1 assert 5 in result_same_name_1 @@ -157,7 +156,6 @@ def test_single_sort_field_desc(self, session): ] assert len(result) == 8 - assert result[0].id == 8 assert 4 in result_same_name_4 assert 6 in result_same_name_4 @@ -250,7 +248,6 @@ def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): ] assert len(result) == 8 - assert result[0].id == 8 assert 4 in result_same_name_4 assert 6 in result_same_name_4 From ee99669cd543ec151f9469c3de25ea28d0a13aae Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sun, 24 Feb 2019 16:15:38 +0000 Subject: [PATCH 18/33] Fix README inline literals and make line sizes consistent --- README.rst | 72 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/README.rst b/README.rst index e159337..c3e340a 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ SQLAlchemy-filters Filtering --------- -Assuming that we have a SQLAlchemy `query` object: +Assuming that we have a SQLAlchemy ``query`` object: .. code-block:: python @@ -61,7 +61,8 @@ Then we can apply filters to that ``query`` object (multiple times): result = filtered_query.all() -It is also possible to filter queries that contain multiple models, including joins: +It is also possible to filter queries that contain multiple models, +including joins: .. code-block:: python @@ -84,7 +85,10 @@ It is also possible to filter queries that contain multiple models, including jo result = filtered_query.all() -`apply_filters` will attempt to automatically join models to `query` if they're not already present and a model-specific filter is supplied. For example, the value of `filtered_query` in the following two code blocks is identical: +``apply_filters`` will attempt to automatically join models to ``query`` +if they're not already present and a model-specific filter is supplied. +For example, the value of ``filtered_query`` in the following two code +blocks is identical: .. code-block:: python @@ -106,11 +110,15 @@ It is also possible to filter queries that contain multiple models, including jo ] filtered_query = apply_filters(query, filter_spec) -The automatic join is only possible if sqlalchemy can implictly determine the condition for the join, for example because of a foreign key relationship. +The automatic join is only possible if sqlalchemy can implictly determine +the condition for the join, for example because of a foreign key relationship. -Automatic joins allow flexibility for clients to filter and sort by related objects without specifying all possible joins on the server beforehand. +Automatic joins allow flexibility for clients to filter and sort by related +objects without specifying all possible joins on the server beforehand. -Note that first filter of the second block does not specify a model. It is implictly applied to the `Foo` model because that is the only model in the original query passed to `apply_filters`. +Note that first filter of the second block does not specify a model. +It is implictly applied to the ``Foo`` model because that is the only +model in the original query passed to ``apply_filters``. It is also possible to apply filters to queries defined by fields or functions: @@ -124,7 +132,7 @@ Restricted Loads ---------------- You can restrict the fields that SQLAlchemy loads from the database by using -the `apply_loads` function: +the ``apply_loads`` function: .. code-block:: python @@ -136,13 +144,18 @@ the `apply_loads` function: query = apply_loads(query, load_spec) # will load only Foo.name and Bar.count -The effect of the `apply_loads` function is to _defer_ the load of any other fields to when/if they're accessed, rather than loading them when the query is executed. It only applies to fields that would be loaded during normal query execution. +The effect of the ``apply_loads`` function is to ``_defer_`` the load +of any other fields to when/if they're accessed, rather than loading +them when the query is executed. It only applies to fields that would be +loaded during normal query execution. Effect on joined queries ^^^^^^^^^^^^^^^^^^^^^^^^ -The default SQLAlchemy join is lazy, meaning that columns from the joined table are loaded only when required. Therefore `apply_loads` has limited effect in the following scenario: +The default SQLAlchemy join is lazy, meaning that columns from the joined +table are loaded only when required. Therefore ``apply_loads`` has limited +effect in the following scenario: .. code-block:: python @@ -154,7 +167,11 @@ The default SQLAlchemy join is lazy, meaning that columns from the joined table query = apply_loads(query, load_spec) # will load only Foo.name -`apply_loads` cannot be applied to columns that are loaded as `joined eager loads `_. This is because a joined eager load does not add the joined model to the original query, as explained `here `_ +``apply_loads`` cannot be applied to columns that are loaded as +`joined eager loads `_. +This is because a joined eager load does not add the joined model to the +original query, as explained +`here `_ The following would not prevent all columns from Bar being eagerly loaded: @@ -169,10 +186,14 @@ The following would not prevent all columns from Bar being eagerly loaded: .. sidebar:: Automatic Join - In fact, what happens here is that `Bar` is automatically joined to `query`, because it is determined that `Bar` is not part of the original query. The `load_spec` therefore has no effect because the automatic join - results in lazy evaluation. + In fact, what happens here is that ``Bar`` is automatically joined + to ``query``, because it is determined that ``Bar`` is not part of + the original query. The ``load_spec`` therefore has no effect because + the automatic join results in lazy evaluation. -If you wish to perform a joined load with restricted columns, you must specify the columns as part of the joined load, rather than with `apply_loads`: +If you wish to perform a joined load with restricted columns, you must +specify the columns as part of the joined load, rather than with +``apply_loads``: .. code-block:: python @@ -201,9 +222,12 @@ Sort result = sorted_query.all() -`apply_sort` will attempt to automatically join models to `query` if they're not already present and a model-specific sort is supplied. The behaviour is the same as in `apply_filters`. +``apply_sort`` will attempt to automatically join models to ``query`` if +they're not already present and a model-specific sort is supplied. +The behaviour is the same as in ``apply_filters``. -This allows flexibility for clients to sort by fields on related objects without specifying all possible joins on the server beforehand. +This allows flexibility for clients to sort by fields on related objects +without specifying all possible joins on the server beforehand. Pagination @@ -240,7 +264,8 @@ following format: # ... ] -The `model` key is optional if the original query being filtered only applies to one model. +The ``model`` key is optional if the original query being filtered only +applies to one model. If there is only one filter, the containing list may be omitted: @@ -249,7 +274,7 @@ If there is only one filter, the containing list may be omitted: filter_spec = {'field': 'field_name', 'op': '==', 'value': 'field_value'} Where ``field`` is the name of the field that will be filtered using the -operator provided in ``op`` (optional, defaults to `==`) and the +operator provided in ``op`` (optional, defaults to ``==``) and the provided ``value`` (optional, depending on the operator). This is the list of operators that can be used: @@ -269,7 +294,8 @@ This is the list of operators that can be used: Boolean Functions ^^^^^^^^^^^^^^^^^ -``and``, ``or``, and ``not`` functions can be used and nested within the filter specification: +``and``, ``or``, and ``not`` functions can be used and nested within the +filter specification: .. code-block:: python @@ -292,7 +318,8 @@ Boolean Functions ] -Note: ``or`` and ``and`` must reference a list of at least one element. ``not`` must reference a list of exactly one element. +Note: ``or`` and ``and`` must reference a list of at least one element. +``not`` must reference a list of exactly one element. Sort format ----------- @@ -311,15 +338,16 @@ applied sequentially: Where ``field`` is the name of the field that will be sorted using the provided ``direction``. -The `model` key is optional if the original query being sorted only applies to one model. +The ``model`` key is optional if the original query being sorted only +applies to one model. Running tests ------------- The default configuration uses **SQLite**, **MySQL** (if the driver is -installed, which is the case when `tox` is used) and **PostgreSQL** (if -the driver is installed, which is the case when `tox` is used) to run +installed, which is the case when ``tox`` is used) and **PostgreSQL** (if +the driver is installed, which is the case when ``tox`` is used) to run the tests, with the following URIs: .. code-block:: shell From cb3a4aa5d9b467e38f12c7567b7445e24c3136ac Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sun, 24 Feb 2019 16:27:02 +0000 Subject: [PATCH 19/33] Make README line sizes consistent --- README.rst | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index c3e340a..94a379d 100644 --- a/README.rst +++ b/README.rst @@ -110,17 +110,20 @@ blocks is identical: ] filtered_query = apply_filters(query, filter_spec) -The automatic join is only possible if sqlalchemy can implictly determine -the condition for the join, for example because of a foreign key relationship. +The automatic join is only possible if sqlalchemy can implictly +determine the condition for the join, for example because of a foreign +key relationship. -Automatic joins allow flexibility for clients to filter and sort by related -objects without specifying all possible joins on the server beforehand. +Automatic joins allow flexibility for clients to filter and sort by +related objects without specifying all possible joins on the server +beforehand. Note that first filter of the second block does not specify a model. It is implictly applied to the ``Foo`` model because that is the only model in the original query passed to ``apply_filters``. -It is also possible to apply filters to queries defined by fields or functions: +It is also possible to apply filters to queries defined by fields or +functions: .. code-block:: python @@ -131,8 +134,8 @@ It is also possible to apply filters to queries defined by fields or functions: Restricted Loads ---------------- -You can restrict the fields that SQLAlchemy loads from the database by using -the ``apply_loads`` function: +You can restrict the fields that SQLAlchemy loads from the database by +using the ``apply_loads`` function: .. code-block:: python @@ -153,9 +156,9 @@ loaded during normal query execution. Effect on joined queries ^^^^^^^^^^^^^^^^^^^^^^^^ -The default SQLAlchemy join is lazy, meaning that columns from the joined -table are loaded only when required. Therefore ``apply_loads`` has limited -effect in the following scenario: +The default SQLAlchemy join is lazy, meaning that columns from the +joined table are loaded only when required. Therefore ``apply_loads`` +has limited effect in the following scenario: .. code-block:: python @@ -173,7 +176,8 @@ This is because a joined eager load does not add the joined model to the original query, as explained `here `_ -The following would not prevent all columns from Bar being eagerly loaded: +The following would not prevent all columns from Bar being eagerly +loaded: .. code-block:: python @@ -188,8 +192,8 @@ The following would not prevent all columns from Bar being eagerly loaded: In fact, what happens here is that ``Bar`` is automatically joined to ``query``, because it is determined that ``Bar`` is not part of - the original query. The ``load_spec`` therefore has no effect because - the automatic join results in lazy evaluation. + the original query. The ``load_spec`` therefore has no effect + because the automatic join results in lazy evaluation. If you wish to perform a joined load with restricted columns, you must specify the columns as part of the joined load, rather than with @@ -346,9 +350,9 @@ Running tests ------------- The default configuration uses **SQLite**, **MySQL** (if the driver is -installed, which is the case when ``tox`` is used) and **PostgreSQL** (if -the driver is installed, which is the case when ``tox`` is used) to run -the tests, with the following URIs: +installed, which is the case when ``tox`` is used) and **PostgreSQL** +(if the driver is installed, which is the case when ``tox`` is used) to +run the tests, with the following URIs: .. code-block:: shell @@ -405,7 +409,8 @@ The following RDBMS are supported (tested): Python 2 -------- -There is no active support for python 2, however it is compatiable as of February 2019, if you install ``funcsigs``. +There is no active support for python 2, however it is compatiable as of +February 2019, if you install ``funcsigs``. License From 7523dff658abea1297214989d9b63f7c89272cb9 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Sun, 24 Feb 2019 18:04:27 +0000 Subject: [PATCH 20/33] Amend docstring --- test/interface/test_sorting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 2f7b0e2..0596322 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -108,11 +108,11 @@ class TestSortApplied(object): """Tests that results are sorted only according to the provided filters. - Exclude nonexplicit sorting from tests: do NOT test how different + Excludes nonexplicit sorting from tests: do NOT test how different RDBMS sort rows with the same values being ordered since this is not consistent across RDBMS. - Exclude `NULL` sorting from tests: sorting fields containing `NULL` + Excludes `NULL` sorting from tests: sorting fields containing `NULL` values is NOT tested since different RDBMS behave differently when sorting `NULL` values. SQL defines that `NULL` values should be placed together when sorting, but it does not specify whether they From 462b1022b35c8861085bf034b8ecd2e062c9c5dc Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 18:30:00 +0000 Subject: [PATCH 21/33] Amend typo in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 94a379d..3972e6a 100644 --- a/README.rst +++ b/README.rst @@ -409,7 +409,7 @@ The following RDBMS are supported (tested): Python 2 -------- -There is no active support for python 2, however it is compatiable as of +There is no active support for python 2, however it is compatible as of February 2019, if you install ``funcsigs``. From bd2c7a1391ba5751af16288820fbc538359850f4 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 18:37:27 +0000 Subject: [PATCH 22/33] Remove unnecessary constant --- test/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 8916eb5..164cc3e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -12,8 +12,6 @@ MYSQL_TEST_DB_URI = 'MYSQL_TEST_DB_URI' POSTGRESQL_TEST_DB_URI = 'POSTGRESQL_TEST_DB_URI' -POSTGRESQL_DB = 'postgresql' - def pytest_addoption(parser): parser.addoption( @@ -98,7 +96,7 @@ def db_uri(request, config): @pytest.fixture(scope='session') def is_postgresql(db_uri): - if POSTGRESQL_DB in db_uri: + if 'postgresql' in db_uri: return True return False From a6bf6d5393961d364790554ad0a1de5f4c28c7ee Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 18:44:18 +0000 Subject: [PATCH 23/33] Use Docker containers for tests in Travis CI --- .travis.yml | 9 +++++---- Makefile | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index dddcece..68c2616 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,13 @@ python: 3.7 dist: xenial -# Keep DB versions synchronized with the ones in the Makefile for local testing services: - - mysql + - docker -addons: - postgresql: "9.6" +before_install: + - docker --version + - make docker-mysql-run + - make docker-postgres-run install: - pip install tox diff --git a/Makefile b/Makefile index 72dea60..26bd7dd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ .PHONY: test -# Versions used by Travis CI. Keep them synchronized. POSTGRES_VERSION?=9.6 MYSQL_VERSION?=5.7 From aa3cf8673a95f19af818e6f359fb9086a5247eef Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 18:52:36 +0000 Subject: [PATCH 24/33] Use Travis addons to update Docker via apt --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 68c2616..d4d199b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,12 @@ dist: xenial services: - docker +addons: + apt: + packages: + - docker-ce + before_install: - - docker --version - make docker-mysql-run - make docker-postgres-run From 7c97fe6e6d1838d1d550effa96279e9b7f8d1bf9 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 19:06:01 +0000 Subject: [PATCH 25/33] Travis CI apt update --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d4d199b..24d3a06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,12 @@ services: addons: apt: + update: true packages: - docker-ce before_install: + - docker --version - make docker-mysql-run - make docker-postgres-run From cdffb252a9b5814c843b12a472d9df82b062f8bd Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 19:18:52 +0000 Subject: [PATCH 26/33] Remove Travis apt addons: it has no effect and doubles run time --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24d3a06..dd118b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,14 +8,7 @@ dist: xenial services: - docker -addons: - apt: - update: true - packages: - - docker-ce - before_install: - - docker --version - make docker-mysql-run - make docker-postgres-run From 767f7dd9049cb229bb04ff40b6afb9e5a7e84b8c Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 19:43:05 +0000 Subject: [PATCH 27/33] Improve tests documentation --- test/interface/test_sorting.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 0596322..a68c665 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -108,15 +108,15 @@ class TestSortApplied(object): """Tests that results are sorted only according to the provided filters. - Excludes nonexplicit sorting from tests: do NOT test how different - RDBMS sort rows with the same values being ordered since this is not - consistent across RDBMS. - - Excludes `NULL` sorting from tests: sorting fields containing `NULL` - values is NOT tested since different RDBMS behave differently when - sorting `NULL` values. SQL defines that `NULL` values should be - placed together when sorting, but it does not specify whether they - should be placed first or last in the result. + Does NOT test how rows with the same values are sorted since this is + not consistent across RDBMS. + + Does NOT test whether `NULL` field values are placed first or last + when sorting since this may differ across RDBMSs. + + SQL defines that `NULL` values should be placed together when + sorting, but it does not specify whether they should be placed first + or last. """ @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') From 56a034d21ddbe1b30fc8df265ccf263731cbafa5 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 20:18:54 +0000 Subject: [PATCH 28/33] Rename variables in tests --- test/interface/test_sorting.py | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index a68c665..76ab1cc 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -127,19 +127,19 @@ def test_single_sort_field_asc(self, session): sorted_query = apply_sort(query, order_by) result = sorted_query.all() - result_same_name_1 = [ + results_with_same_order_1 = [ result[0].id, result[1].id, result[2].id, result[3].id ] - result_same_name_4 = [result[5].id, result[6].id] + results_with_same_order_2 = [result[5].id, result[6].id] assert len(result) == 8 - assert 1 in result_same_name_1 - assert 3 in result_same_name_1 - assert 5 in result_same_name_1 - assert 7 in result_same_name_1 + assert 1 in results_with_same_order_1 + assert 3 in results_with_same_order_1 + assert 5 in results_with_same_order_1 + assert 7 in results_with_same_order_1 assert result[4].id == 2 - assert 4 in result_same_name_4 - assert 6 in result_same_name_4 + assert 4 in results_with_same_order_2 + assert 6 in results_with_same_order_2 assert result[7].id == 8 @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') @@ -150,20 +150,20 @@ def test_single_sort_field_desc(self, session): sorted_query = apply_sort(query, order_by) result = sorted_query.all() - result_same_name_4 = [result[1].id, result[2].id] - result_same_name_1 = [ + results_with_same_order_1 = [result[1].id, result[2].id] + results_with_same_order_2 = [ result[4].id, result[5].id, result[6].id, result[7].id ] assert len(result) == 8 assert result[0].id == 8 - assert 4 in result_same_name_4 - assert 6 in result_same_name_4 + assert 4 in results_with_same_order_1 + assert 6 in results_with_same_order_1 assert result[3].id == 2 - assert 1 in result_same_name_1 - assert 3 in result_same_name_1 - assert 5 in result_same_name_1 - assert 7 in result_same_name_1 + assert 1 in results_with_same_order_2 + assert 3 in results_with_same_order_2 + assert 5 in results_with_same_order_2 + assert 7 in results_with_same_order_2 @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_multiple_sort_fields(self, session): @@ -242,20 +242,20 @@ def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): sorted_query = apply_sort(query, sort_spec) result = sorted_query.all() - result_same_name_4 = [result[1].id, result[2].id] - result_same_name_1 = [ + results_with_same_order_1 = [result[1].id, result[2].id] + results_with_same_order_2 = [ result[4].id, result[5].id, result[6].id, result[7].id ] assert len(result) == 8 assert result[0].id == 8 - assert 4 in result_same_name_4 - assert 6 in result_same_name_4 + assert 4 in results_with_same_order_1 + assert 6 in results_with_same_order_1 assert result[3].id == 2 - assert 1 in result_same_name_1 - assert 3 in result_same_name_1 - assert 5 in result_same_name_1 - assert 7 in result_same_name_1 + assert 1 in results_with_same_order_2 + assert 3 in results_with_same_order_2 + assert 5 in results_with_same_order_2 + assert 7 in results_with_same_order_2 class TestAutoJoin: From 6af450c26a5998e4c4dbbb7fc85f1a2b5143f06b Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Wed, 27 Feb 2019 20:26:42 +0000 Subject: [PATCH 29/33] Fix CHANGELOG syntax --- CHANGELOG.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ff98e50..e18deae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,7 @@ Version 0.8.0 Released 2018-06-25 -* Adds support for `ilike` (case-insensitive) string comparison. +* Adds support for ``ilike`` (case-insensitive) string comparison. Version 0.7.0 @@ -51,7 +51,8 @@ Released 2017-05-22 * Adds support for boolean functions within filters * Adds the possibility of supplying a single dictionary as filters when only one filter is provided -* Makes the `op` filter attribute optional: `==` is the default operator +* Makes the ``op`` filter attribute optional: ``==`` is the default + operator Version 0.2.0 ------------- From e45d260828c3f395c36af2df64842338c0258d3d Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 28 Feb 2019 19:34:15 +0000 Subject: [PATCH 30/33] Improve test assertions and make variable name consistent --- test/interface/test_sorting.py | 160 ++++++++++++++------------------- 1 file changed, 68 insertions(+), 92 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 76ab1cc..18357ef 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -125,22 +125,14 @@ def test_single_sort_field_asc(self, session): order_by = [{'field': 'name', 'direction': 'asc'}] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() + results = sorted_query.all() - results_with_same_order_1 = [ - result[0].id, result[1].id, result[2].id, result[3].id + assert [result.name for result in results] == [ + 'name_1', 'name_1', 'name_1', 'name_1', + 'name_2', + 'name_4', 'name_4', + 'name_5', ] - results_with_same_order_2 = [result[5].id, result[6].id] - - assert len(result) == 8 - assert 1 in results_with_same_order_1 - assert 3 in results_with_same_order_1 - assert 5 in results_with_same_order_1 - assert 7 in results_with_same_order_1 - assert result[4].id == 2 - assert 4 in results_with_same_order_2 - assert 6 in results_with_same_order_2 - assert result[7].id == 8 @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_single_sort_field_desc(self, session): @@ -148,23 +140,15 @@ def test_single_sort_field_desc(self, session): order_by = [{'field': 'name', 'direction': 'desc'}] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() + results = sorted_query.all() - results_with_same_order_1 = [result[1].id, result[2].id] - results_with_same_order_2 = [ - result[4].id, result[5].id, result[6].id, result[7].id + assert [result.name for result in results] == [ + 'name_5', + 'name_4', 'name_4', + 'name_2', + 'name_1', 'name_1', 'name_1', 'name_1', ] - assert len(result) == 8 - assert result[0].id == 8 - assert 4 in results_with_same_order_1 - assert 6 in results_with_same_order_1 - assert result[3].id == 2 - assert 1 in results_with_same_order_2 - assert 3 in results_with_same_order_2 - assert 5 in results_with_same_order_2 - assert 7 in results_with_same_order_2 - @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_multiple_sort_fields(self, session): query = session.query(Bar) @@ -175,17 +159,17 @@ def test_multiple_sort_fields(self, session): ] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() - - assert len(result) == 8 - assert result[0].id == 1 - assert result[1].id == 3 - assert result[2].id == 7 - assert result[3].id == 5 - assert result[4].id == 2 - assert result[5].id == 6 - assert result[6].id == 4 - assert result[7].id == 8 + results = sorted_query.all() + + assert len(results) == 8 + assert results[0].id == 1 + assert results[1].id == 3 + assert results[2].id == 7 + assert results[3].id == 5 + assert results[4].id == 2 + assert results[5].id == 6 + assert results[6].id == 4 + assert results[7].id == 8 def test_multiple_models(self, session): @@ -226,13 +210,13 @@ def test_multiple_models(self, session): ] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() + results = sorted_query.all() - assert len(result) == 4 - assert result[0].id == 3 - assert result[1].id == 1 - assert result[2].id == 4 - assert result[3].id == 2 + assert len(results) == 4 + assert results[0].id == 3 + assert results[1].id == 1 + assert results[2].id == 4 + assert results[3].id == 2 @pytest.mark.usefixtures('multiple_bars_with_no_nulls_inserted') def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): @@ -240,23 +224,15 @@ def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): sort_spec = {'field': 'name', 'direction': 'desc'} sorted_query = apply_sort(query, sort_spec) - result = sorted_query.all() + results = sorted_query.all() - results_with_same_order_1 = [result[1].id, result[2].id] - results_with_same_order_2 = [ - result[4].id, result[5].id, result[6].id, result[7].id + assert [result.name for result in results] == [ + 'name_5', + 'name_4', 'name_4', + 'name_2', + 'name_1', 'name_1', 'name_1', 'name_1', ] - assert len(result) == 8 - assert result[0].id == 8 - assert 4 in results_with_same_order_1 - assert 6 in results_with_same_order_1 - assert result[3].id == 2 - assert 1 in results_with_same_order_2 - assert 3 in results_with_same_order_2 - assert 5 in results_with_same_order_2 - assert 7 in results_with_same_order_2 - class TestAutoJoin: @@ -271,17 +247,17 @@ def test_auto_join(self, session): ] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() - - assert len(result) == 8 - assert result[0].id == 5 - assert result[1].id == 7 - assert result[2].id == 6 - assert result[3].id == 8 - assert result[4].id == 1 - assert result[5].id == 3 - assert result[6].id == 2 - assert result[7].id == 4 + results = sorted_query.all() + + assert len(results) == 8 + assert results[0].id == 5 + assert results[1].id == 7 + assert results[2].id == 6 + assert results[3].id == 8 + assert results[4].id == 1 + assert results[5].id == 3 + assert results[6].id == 2 + assert results[7].id == 4 @pytest.mark.usefixtures('multiple_foos_inserted') def test_noop_if_query_contains_named_models(self, session): @@ -294,17 +270,17 @@ def test_noop_if_query_contains_named_models(self, session): ] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() - - assert len(result) == 8 - assert result[0].id == 5 - assert result[1].id == 7 - assert result[2].id == 6 - assert result[3].id == 8 - assert result[4].id == 1 - assert result[5].id == 3 - assert result[6].id == 2 - assert result[7].id == 4 + results = sorted_query.all() + + assert len(results) == 8 + assert results[0].id == 5 + assert results[1].id == 7 + assert results[2].id == 6 + assert results[3].id == 8 + assert results[4].id == 1 + assert results[5].id == 3 + assert results[6].id == 2 + assert results[7].id == 4 @pytest.mark.usefixtures('multiple_foos_inserted') def test_auto_join_to_invalid_model(self, session): @@ -346,14 +322,14 @@ def test_eager_load(self, session): ] sorted_query = apply_sort(query, order_by) - result = sorted_query.all() - - assert len(result) == 8 - assert result[0].id == 5 - assert result[1].id == 7 - assert result[2].id == 6 - assert result[3].id == 8 - assert result[4].id == 1 - assert result[5].id == 3 - assert result[6].id == 2 - assert result[7].id == 4 + results = sorted_query.all() + + assert len(results) == 8 + assert results[0].id == 5 + assert results[1].id == 7 + assert results[2].id == 6 + assert results[3].id == 8 + assert results[4].id == 1 + assert results[5].id == 3 + assert results[6].id == 2 + assert results[7].id == 4 From 3c4bde452fc047c20c0a2aef9c8346e265ad0122 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 28 Feb 2019 21:35:28 +0000 Subject: [PATCH 31/33] Amend MySQL test container name --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 26bd7dd..cdbdd1d 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ coverage: flake8 rst-lint # Docker test containers docker-mysql-run: - docker run -d --name mysql-postgres-sqlalchemy-filters -p 3306:3306 \ + docker run -d --name mysql-sqlalchemy-filters -p 3306:3306 \ -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ mysql:$(MYSQL_VERSION) From ce1ca02986dd12b051796f72ef5bb905cba4e98a Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Thu, 28 Feb 2019 21:35:50 +0000 Subject: [PATCH 32/33] Automatically clean up test docker containers --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cdbdd1d..10b9e90 100644 --- a/Makefile +++ b/Makefile @@ -22,12 +22,12 @@ coverage: flake8 rst-lint # Docker test containers docker-mysql-run: - docker run -d --name mysql-sqlalchemy-filters -p 3306:3306 \ + docker run -d --rm --name mysql-sqlalchemy-filters -p 3306:3306 \ -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ mysql:$(MYSQL_VERSION) docker-postgres-run: - docker run -d --name postgres-sqlalchemy-filters -p 5432:5432 \ + docker run -d --rm --name postgres-sqlalchemy-filters -p 5432:5432 \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD= \ -e POSTGRES_DB=test_sqlalchemy_filters \ From e9155eb3765f6fb0460032ef1fc052220c2ea2c2 Mon Sep 17 00:00:00 2001 From: Julio Trigo Date: Tue, 5 Mar 2019 22:55:26 +0000 Subject: [PATCH 33/33] Improve assertions in sorting tests --- test/interface/test_sorting.py | 120 +++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/test/interface/test_sorting.py b/test/interface/test_sorting.py index 18357ef..3addf9d 100644 --- a/test/interface/test_sorting.py +++ b/test/interface/test_sorting.py @@ -12,7 +12,7 @@ @pytest.fixture -def multiple_foos_inserted(session, multiple_bars_with_no_nulls_inserted): +def multiple_foos_inserted(session): foo_1 = Foo(id=1, bar_id=1, name='name_1', count=1) foo_2 = Foo(id=2, bar_id=2, name='name_2', count=1) foo_3 = Foo(id=3, bar_id=3, name='name_1', count=1) @@ -161,15 +161,18 @@ def test_multiple_sort_fields(self, session): sorted_query = apply_sort(query, order_by) results = sorted_query.all() - assert len(results) == 8 - assert results[0].id == 1 - assert results[1].id == 3 - assert results[2].id == 7 - assert results[3].id == 5 - assert results[4].id == 2 - assert results[5].id == 6 - assert results[6].id == 4 - assert results[7].id == 8 + assert [ + (result.name, result.count, result.id) for result in results + ] == [ + ('name_1', 5, 1), + ('name_1', 3, 3), + ('name_1', 2, 7), + ('name_1', 2, 5), + ('name_2', 10, 2), + ('name_4', 15, 6), + ('name_4', 12, 4), + ('name_5', 1, 8), + ] def test_multiple_models(self, session): @@ -236,9 +239,11 @@ def test_a_single_dict_can_be_supplied_as_sort_spec(self, session): class TestAutoJoin: - @pytest.mark.usefixtures('multiple_foos_inserted') + @pytest.mark.usefixtures( + 'multiple_bars_with_no_nulls_inserted', + 'multiple_foos_inserted' + ) def test_auto_join(self, session): - query = session.query(Foo) order_by = [ {'field': 'count', 'direction': 'desc'}, @@ -249,19 +254,24 @@ def test_auto_join(self, session): sorted_query = apply_sort(query, order_by) results = sorted_query.all() - assert len(results) == 8 - assert results[0].id == 5 - assert results[1].id == 7 - assert results[2].id == 6 - assert results[3].id == 8 - assert results[4].id == 1 - assert results[5].id == 3 - assert results[6].id == 2 - assert results[7].id == 4 - - @pytest.mark.usefixtures('multiple_foos_inserted') - def test_noop_if_query_contains_named_models(self, session): + assert [ + (result.count, result.bar.name, result.id) for result in results + ] == [ + (2, 'name_1', 5), + (2, 'name_1', 7), + (2, 'name_4', 6), + (2, 'name_5', 8), + (1, 'name_1', 1), + (1, 'name_1', 3), + (1, 'name_2', 2), + (1, 'name_4', 4), + ] + @pytest.mark.usefixtures( + 'multiple_bars_with_no_nulls_inserted', + 'multiple_foos_inserted' + ) + def test_noop_if_query_contains_named_models(self, session): query = session.query(Foo).join(Bar) order_by = [ {'model': 'Foo', 'field': 'count', 'direction': 'desc'}, @@ -272,19 +282,24 @@ def test_noop_if_query_contains_named_models(self, session): sorted_query = apply_sort(query, order_by) results = sorted_query.all() - assert len(results) == 8 - assert results[0].id == 5 - assert results[1].id == 7 - assert results[2].id == 6 - assert results[3].id == 8 - assert results[4].id == 1 - assert results[5].id == 3 - assert results[6].id == 2 - assert results[7].id == 4 - - @pytest.mark.usefixtures('multiple_foos_inserted') - def test_auto_join_to_invalid_model(self, session): + assert [ + (result.count, result.bar.name, result.id) for result in results + ] == [ + (2, 'name_1', 5), + (2, 'name_1', 7), + (2, 'name_4', 6), + (2, 'name_5', 8), + (1, 'name_1', 1), + (1, 'name_1', 3), + (1, 'name_2', 2), + (1, 'name_4', 4), + ] + @pytest.mark.usefixtures( + 'multiple_bars_with_no_nulls_inserted', + 'multiple_foos_inserted' + ) + def test_auto_join_to_invalid_model(self, session): query = session.query(Foo) order_by = [ {'model': 'Foo', 'field': 'count', 'direction': 'desc'}, @@ -297,9 +312,11 @@ def test_auto_join_to_invalid_model(self, session): assert 'The query does not contain model `Qux`.' == err.value.args[0] - @pytest.mark.usefixtures('multiple_foos_inserted') + @pytest.mark.usefixtures( + 'multiple_bars_with_no_nulls_inserted', + 'multiple_foos_inserted' + ) def test_ambiguous_query(self, session): - query = session.query(Foo).join(Bar) order_by = [ {'field': 'count', 'direction': 'asc'}, # ambiguous @@ -310,9 +327,11 @@ def test_ambiguous_query(self, session): assert 'Ambiguous spec. Please specify a model.' == err.value.args[0] - @pytest.mark.usefixtures('multiple_foos_inserted') + @pytest.mark.usefixtures( + 'multiple_bars_with_no_nulls_inserted', + 'multiple_foos_inserted' + ) def test_eager_load(self, session): - # behaves as if the joinedload wasn't present query = session.query(Foo).options(joinedload(Foo.bar)) order_by = [ @@ -324,12 +343,15 @@ def test_eager_load(self, session): sorted_query = apply_sort(query, order_by) results = sorted_query.all() - assert len(results) == 8 - assert results[0].id == 5 - assert results[1].id == 7 - assert results[2].id == 6 - assert results[3].id == 8 - assert results[4].id == 1 - assert results[5].id == 3 - assert results[6].id == 2 - assert results[7].id == 4 + assert [ + (result.count, result.bar.name, result.id) for result in results + ] == [ + (2, 'name_1', 5), + (2, 'name_1', 7), + (2, 'name_4', 6), + (2, 'name_5', 8), + (1, 'name_1', 1), + (1, 'name_1', 3), + (1, 'name_2', 2), + (1, 'name_4', 4), + ]