From b31c9f980e37220422f1488fb82c93745b94bec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Franz=C3=A9n?= Date: Fri, 13 Nov 2020 10:00:22 +0100 Subject: [PATCH 1/5] add test workflow (#1) * add test workflow * Fix flaky test: Sleep one sec after first commit so that the second commit does not happen the same second * Use SkipTest to get rid of --- .github/workflows/test-pr.yml | 33 ++++++++++++++++++++++ sqlalchemy_mixins/tests/test_smartquery.py | 3 +- sqlalchemy_mixins/tests/test_timestamp.py | 4 +-- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/test-pr.yml diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml new file mode 100644 index 0000000..3f02ebe --- /dev/null +++ b/.github/workflows/test-pr.yml @@ -0,0 +1,33 @@ +name: Test PR + +on: + pull_request: + branches: [ master ] +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r requirements-dev.txt + - name: Run tests + run: | + nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins + export PYTHONPATH=.:$PYTHONPATH + python examples/activerecord.py + python examples/all_features.py + python examples/eagerload.py + python examples/repr.py + python examples/smartquery.py + python examples/serialize.py + python examples/timestamp.py \ No newline at end of file diff --git a/sqlalchemy_mixins/tests/test_smartquery.py b/sqlalchemy_mixins/tests/test_smartquery.py index 0cb9a78..80dc241 100644 --- a/sqlalchemy_mixins/tests/test_smartquery.py +++ b/sqlalchemy_mixins/tests/test_smartquery.py @@ -1,6 +1,7 @@ import unittest import datetime +import nose import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy import event @@ -785,7 +786,7 @@ def test_explicitly_set_in_schema_subqueryload(self): self.assertEqual(self.query_count, 2) # TODO: implement below logic - @unittest.expectedFailure + @nose.SkipTest def test_override_eagerload_method_in_schema(self): """ here we use 'post' relation in filters, diff --git a/sqlalchemy_mixins/tests/test_timestamp.py b/sqlalchemy_mixins/tests/test_timestamp.py index 8e2fa10..3976a50 100644 --- a/sqlalchemy_mixins/tests/test_timestamp.py +++ b/sqlalchemy_mixins/tests/test_timestamp.py @@ -1,6 +1,6 @@ import unittest +import time from datetime import datetime - import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base @@ -69,7 +69,7 @@ def test_updated_at_column_must_change_value(self): """Test whether updated_at value is most recently after update.""" user = self.session.query(User).first() dt_1 = user.updated_at - + time.sleep(1) user.name = 'New name' self.session.commit() From 7eba71c73a5a7da923d31c39cae8f0d1fa92c02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Franz=C3=A9n?= Date: Fri, 13 Nov 2020 10:20:25 +0100 Subject: [PATCH 2/5] _entity_zero() has moved from query to context (I believe), perhaps there's a better way to get _entity_zero, I haven't spent any time looking --- requirements.txt | 2 +- sqlalchemy_mixins/smartquery.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 240e8c1..d3296e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -sqlalchemy >= 1.0 +sqlalchemy >= 1.4.0b1 six typing; python_version < '3.5' diff --git a/sqlalchemy_mixins/smartquery.py b/sqlalchemy_mixins/smartquery.py index ac2aff5..ea45550 100644 --- a/sqlalchemy_mixins/smartquery.py +++ b/sqlalchemy_mixins/smartquery.py @@ -84,7 +84,7 @@ def smart_query(query, filters=None, sort_attrs=None, schema=None): schema = {} # noinspection PyProtectedMember - root_cls = query._entity_zero().class_ # for example, User or Post + root_cls = query._compile_state()._entity_zero().class_ # for example, User or Post attrs = list(filters.keys()) + \ list(map(lambda s: s.lstrip(DESC_PREFIX), sort_attrs)) aliases = OrderedDict({}) From 144d00730626f2fa19fa63ce75ce1b7c2948d214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Franz=C3=A9n?= Date: Fri, 13 Nov 2020 14:13:57 +0100 Subject: [PATCH 3/5] Make it work in sqlalchemy>=1.4.0 --- .github/workflows/test-pr.yml | 14 ++++++++- requirements.txt | 2 +- sqlalchemy_mixins/smartquery.py | 22 +++++++++++++- sqlalchemy_mixins/tests/test_smartquery.py | 34 +++++++++++++++++++++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 3f02ebe..eee18ff 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -20,8 +20,20 @@ jobs: run: | python -m pip install --upgrade pip setuptools wheel pip install -r requirements-dev.txt - - name: Run tests + - name: Run tests with sqlalchemy current run: | + nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins + export PYTHONPATH=.:$PYTHONPATH + python examples/activerecord.py + python examples/all_features.py + python examples/eagerload.py + python examples/repr.py + python examples/smartquery.py + python examples/serialize.py + python examples/timestamp.py + - name: Run tests with sqlalchemy next + run: | + pip install sqlalchemy --pre nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins export PYTHONPATH=.:$PYTHONPATH python examples/activerecord.py diff --git a/requirements.txt b/requirements.txt index d3296e8..240e8c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -sqlalchemy >= 1.4.0b1 +sqlalchemy >= 1.0 six typing; python_version < '3.5' diff --git a/sqlalchemy_mixins/smartquery.py b/sqlalchemy_mixins/smartquery.py index ea45550..3a92a61 100644 --- a/sqlalchemy_mixins/smartquery.py +++ b/sqlalchemy_mixins/smartquery.py @@ -62,6 +62,17 @@ def _parse_path_and_make_aliases(entity, entity_path, attrs, aliases): aliases[path] = alias, relationship _parse_path_and_make_aliases(alias, path, nested_attrs, aliases) +def _get_root_cls(query): + # sqlalchemy < 1.4.0 + if hasattr(query, '_entity_zero'): + return query._entity_zero().class_ + + # sqlalchemy >= 1.4.0 + else: + if hasattr(query, '_entity_from_pre_ent_zero'): + return query._entity_from_pre_ent_zero().class_ + raise ValueError('Cannot get a root class from`{}`' + .format(query)) def smart_query(query, filters=None, sort_attrs=None, schema=None): """ @@ -83,8 +94,17 @@ def smart_query(query, filters=None, sort_attrs=None, schema=None): if not schema: schema = {} + # sqlalchemy >= 1.4.0, should probably a. check something else to determine if we need to convert + # AppenderQuery to a query, b. probably not hack it like this # noinspection PyProtectedMember - root_cls = query._compile_state()._entity_zero().class_ # for example, User or Post + if type(query).__name__ == 'AppenderQuery' and query._statement: + sess = query.session + # noinspection PyProtectedMember + query = query._statement + query.session = sess + + # noinspection PyProtectedMember + root_cls = _get_root_cls(query) # for example, User or Post attrs = list(filters.keys()) + \ list(map(lambda s: s.lstrip(DESC_PREFIX), sort_attrs)) aliases = OrderedDict({}) diff --git a/sqlalchemy_mixins/tests/test_smartquery.py b/sqlalchemy_mixins/tests/test_smartquery.py index 80dc241..942db95 100644 --- a/sqlalchemy_mixins/tests/test_smartquery.py +++ b/sqlalchemy_mixins/tests/test_smartquery.py @@ -33,6 +33,13 @@ class User(BaseModel): # not to be a backref posts = sa.orm.relationship('Post') comments = sa.orm.relationship('Comment') + # below relationship will just return query (without executing) + # this query can be customized + # see http://docs.sqlalchemy.org/en/latest/orm/collections.html#dynamic-relationship + # + # we will use this relationship for demonstrating real-life example + # of how smart_query() function works (see 3.2.2) + comments_ = sa.orm.relationship('Comment', lazy="dynamic") # this will return query class Post(BaseModel): @@ -217,7 +224,7 @@ def test_filterable_attributes(self): self.assertEqual(set(User.filterable_attributes), {'id', 'name', # normal columns - 'posts', 'comments' # relations + 'posts', 'comments', 'comments_' # relations }) self.assertNotIn('posts_viewonly', set(User.filterable_attributes)) @@ -785,6 +792,31 @@ def test_explicitly_set_in_schema_subqueryload(self): _ = res[0].post.comments self.assertEqual(self.query_count, 2) + def test_lazy_dynamic(self): + u1, u2, u3, p11, p12, p21, p22, cm11, cm12, cm21, cm22, cm_empty = \ + self._seed() + + schema = { + 'post': { + 'user': JOINED + } + } + + user = sess.query(User).first() + # and we have initial query for his/her comments + # (see User.comments_ relationship) + query = user.comments_ + # now we just smartly apply all filters, sorts and eagerload. Perfect! + res = smart_query(query, + filters={ + 'post___public': True, + 'user__isnull': False + }, + sort_attrs=['user___name', '-created_at'], + schema=schema).all() + + assert res[0] == cm21 + # TODO: implement below logic @nose.SkipTest def test_override_eagerload_method_in_schema(self): From f670ead6ff9865c56bd819cbae89248627514b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Franz=C3=A9n?= Date: Fri, 13 Nov 2020 14:17:28 +0100 Subject: [PATCH 4/5] Fix testing of sqlalchemy next --- .github/workflows/test-pr.yml | 4 +++- sqlalchemy_mixins/smartquery.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index eee18ff..ce85086 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -1,6 +1,8 @@ name: Test PR on: + push: + branches: [ master ] pull_request: branches: [ master ] jobs: @@ -33,7 +35,7 @@ jobs: python examples/timestamp.py - name: Run tests with sqlalchemy next run: | - pip install sqlalchemy --pre + pip install --upgrade sqlalchemy --pre nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins export PYTHONPATH=.:$PYTHONPATH python examples/activerecord.py diff --git a/sqlalchemy_mixins/smartquery.py b/sqlalchemy_mixins/smartquery.py index 3a92a61..42aa2cb 100644 --- a/sqlalchemy_mixins/smartquery.py +++ b/sqlalchemy_mixins/smartquery.py @@ -103,7 +103,6 @@ def smart_query(query, filters=None, sort_attrs=None, schema=None): query = query._statement query.session = sess - # noinspection PyProtectedMember root_cls = _get_root_cls(query) # for example, User or Post attrs = list(filters.keys()) + \ list(map(lambda s: s.lstrip(DESC_PREFIX), sort_attrs)) From 98a0177156bbc96a0a3f6bb62fd9a59a69d817f0 Mon Sep 17 00:00:00 2001 From: Michael Bukachi Date: Thu, 18 Mar 2021 20:23:37 +0300 Subject: [PATCH 5/5] Minor updates --- .github/workflows/test-pr.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 90cffec..356f941 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -21,8 +21,20 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install -r requirements-dev.txt pip install "sqlalchemy<1.4" - - name: Run tests + - name: Run tests < sqlalchemy1.4 run: | + nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins + export PYTHONPATH=.:$PYTHONPATH + python examples/activerecord.py + python examples/all_features.py + python examples/eagerload.py + python examples/repr.py + python examples/smartquery.py + python examples/serialize.py + python examples/timestamp.py + - name: Run tests >= sqlalchemy1.4 + run: | + pip install -U sqlalchemy nosetests --with-coverage --cover-inclusive --cover-package=sqlalchemy_mixins export PYTHONPATH=.:$PYTHONPATH python examples/activerecord.py