Skip to content

Commit 64d8bb8

Browse files
authored
Merge pull request #2 from colelin26/support_sqlalchemy1.4
Support sqlalchemy1.4
2 parents 2eb712f + 8829d01 commit 64d8bb8

File tree

9 files changed

+95
-130
lines changed

9 files changed

+95
-130
lines changed

.github/workflows/tests.yml

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@ jobs:
88
strategy:
99
max-parallel: 10
1010
matrix:
11-
sql-alchemy: ["1.2", "1.3"]
11+
sql-alchemy: ["1.2", "1.3", "1.4"]
1212
python-version: ["3.6", "3.7", "3.8", "3.9"]
1313

1414
steps:
15-
- uses: actions/checkout@v2
16-
- name: Set up Python ${{ matrix.python-version }}
17-
uses: actions/setup-python@v2
18-
with:
19-
python-version: ${{ matrix.python-version }}
20-
- name: Install dependencies
21-
run: |
22-
python -m pip install --upgrade pip
23-
pip install tox tox-gh-actions
24-
- name: Test with tox
25-
run: tox
26-
env:
27-
SQLALCHEMY: ${{ matrix.sql-alchemy }}
28-
TOXENV: ${{ matrix.toxenv }}
29-
- name: Upload coverage.xml
30-
if: ${{ matrix.sql-alchemy == '1.3' && matrix.python-version == '3.9' }}
31-
uses: actions/upload-artifact@v2
32-
with:
33-
name: graphene-sqlalchemy-coverage
34-
path: coverage.xml
35-
if-no-files-found: error
36-
- name: Upload coverage.xml to codecov
37-
if: ${{ matrix.sql-alchemy == '1.3' && matrix.python-version == '3.9' }}
38-
uses: codecov/codecov-action@v1
15+
- uses: actions/checkout@v2
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install tox tox-gh-actions
24+
- name: Test with tox
25+
run: tox
26+
env:
27+
SQLALCHEMY: ${{ matrix.sql-alchemy }}
28+
TOXENV: ${{ matrix.toxenv }}
29+
- name: Upload coverage.xml
30+
if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.9' }}
31+
uses: actions/upload-artifact@v2
32+
with:
33+
name: graphene-sqlalchemy-coverage
34+
path: coverage.xml
35+
if-no-files-found: error
36+
- name: Upload coverage.xml to codecov
37+
if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.9' }}
38+
uses: codecov/codecov-action@v1

graphene_sqlalchemy/batching.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from sqlalchemy.orm import Session, strategies
44
from sqlalchemy.orm.query import QueryContext
55

6+
from .utils import is_sqlalchemy_version_less_than
7+
68

79
def get_batch_resolver(relationship_prop):
810

@@ -52,15 +54,30 @@ async def batch_load_fn(self, parents):
5254
states = [(sqlalchemy.inspect(parent), True) for parent in parents]
5355

5456
# For our purposes, the query_context will only used to get the session
55-
query_context = QueryContext(session.query(parent_mapper.entity))
56-
57-
selectin_loader._load_for_path(
58-
query_context,
59-
parent_mapper._path_registry,
60-
states,
61-
None,
62-
child_mapper,
63-
)
57+
query_context = None
58+
if is_sqlalchemy_version_less_than('1.4'):
59+
query_context = QueryContext(session.query(parent_mapper.entity))
60+
else:
61+
parent_mapper_query = session.query(parent_mapper.entity)
62+
query_context = parent_mapper_query._compile_context()
63+
64+
if is_sqlalchemy_version_less_than('1.4'):
65+
selectin_loader._load_for_path(
66+
query_context,
67+
parent_mapper._path_registry,
68+
states,
69+
None,
70+
child_mapper
71+
)
72+
else:
73+
selectin_loader._load_for_path(
74+
query_context,
75+
parent_mapper._path_registry,
76+
states,
77+
None,
78+
child_mapper,
79+
None
80+
)
6481

6582
return [getattr(parent, relationship_prop.key) for parent in parents]
6683

graphene_sqlalchemy/tests/test_batching.py

Lines changed: 27 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ast
12
import contextlib
23
import logging
34

@@ -9,8 +10,9 @@
910
from ..fields import (BatchSQLAlchemyConnectionField,
1011
default_connection_field_factory)
1112
from ..types import ORMField, SQLAlchemyObjectType
13+
from ..utils import is_sqlalchemy_version_less_than
1214
from .models import Article, HairKind, Pet, Reporter
13-
from .utils import is_sqlalchemy_version_less_than, to_std_dicts
15+
from .utils import remove_cache_miss_stat, to_std_dicts
1416

1517

1618
class MockLoggingHandler(logging.Handler):
@@ -126,26 +128,12 @@ async def test_many_to_one(session_factory):
126128
assert len(sql_statements) == 1
127129
return
128130

129-
assert messages == [
130-
'BEGIN (implicit)',
131-
132-
'SELECT articles.id AS articles_id, '
133-
'articles.headline AS articles_headline, '
134-
'articles.pub_date AS articles_pub_date, '
135-
'articles.reporter_id AS articles_reporter_id \n'
136-
'FROM articles',
137-
'()',
138-
139-
'SELECT reporters.id AS reporters_id, '
140-
'(SELECT CAST(count(reporters.id) AS INTEGER) AS anon_2 \nFROM reporters) AS anon_1, '
141-
'reporters.first_name AS reporters_first_name, '
142-
'reporters.last_name AS reporters_last_name, '
143-
'reporters.email AS reporters_email, '
144-
'reporters.favorite_pet_kind AS reporters_favorite_pet_kind \n'
145-
'FROM reporters \n'
146-
'WHERE reporters.id IN (?, ?)',
147-
'(1, 2)',
148-
]
131+
if not is_sqlalchemy_version_less_than('1.4'):
132+
messages[2] = remove_cache_miss_stat(messages[2])
133+
messages[4] = remove_cache_miss_stat(messages[4])
134+
135+
assert ast.literal_eval(messages[2]) == ()
136+
assert sorted(ast.literal_eval(messages[4])) == [1, 2]
149137

150138
assert not result.errors
151139
result = to_std_dicts(result.data)
@@ -218,26 +206,12 @@ async def test_one_to_one(session_factory):
218206
assert len(sql_statements) == 1
219207
return
220208

221-
assert messages == [
222-
'BEGIN (implicit)',
223-
224-
'SELECT (SELECT CAST(count(reporters.id) AS INTEGER) AS anon_2 \nFROM reporters) AS anon_1, '
225-
'reporters.id AS reporters_id, '
226-
'reporters.first_name AS reporters_first_name, '
227-
'reporters.last_name AS reporters_last_name, '
228-
'reporters.email AS reporters_email, '
229-
'reporters.favorite_pet_kind AS reporters_favorite_pet_kind \n'
230-
'FROM reporters',
231-
'()',
232-
233-
'SELECT articles.reporter_id AS articles_reporter_id, '
234-
'articles.id AS articles_id, '
235-
'articles.headline AS articles_headline, '
236-
'articles.pub_date AS articles_pub_date \n'
237-
'FROM articles \n'
238-
'WHERE articles.reporter_id IN (?, ?)',
239-
'(1, 2)'
240-
]
209+
if not is_sqlalchemy_version_less_than('1.4'):
210+
messages[2] = remove_cache_miss_stat(messages[2])
211+
messages[4] = remove_cache_miss_stat(messages[4])
212+
213+
assert ast.literal_eval(messages[2]) == ()
214+
assert sorted(ast.literal_eval(messages[4])) == [1, 2]
241215

242216
assert not result.errors
243217
result = to_std_dicts(result.data)
@@ -322,26 +296,12 @@ async def test_one_to_many(session_factory):
322296
assert len(sql_statements) == 1
323297
return
324298

325-
assert messages == [
326-
'BEGIN (implicit)',
327-
328-
'SELECT (SELECT CAST(count(reporters.id) AS INTEGER) AS anon_2 \nFROM reporters) AS anon_1, '
329-
'reporters.id AS reporters_id, '
330-
'reporters.first_name AS reporters_first_name, '
331-
'reporters.last_name AS reporters_last_name, '
332-
'reporters.email AS reporters_email, '
333-
'reporters.favorite_pet_kind AS reporters_favorite_pet_kind \n'
334-
'FROM reporters',
335-
'()',
336-
337-
'SELECT articles.reporter_id AS articles_reporter_id, '
338-
'articles.id AS articles_id, '
339-
'articles.headline AS articles_headline, '
340-
'articles.pub_date AS articles_pub_date \n'
341-
'FROM articles \n'
342-
'WHERE articles.reporter_id IN (?, ?)',
343-
'(1, 2)'
344-
]
299+
if not is_sqlalchemy_version_less_than('1.4'):
300+
messages[2] = remove_cache_miss_stat(messages[2])
301+
messages[4] = remove_cache_miss_stat(messages[4])
302+
303+
assert ast.literal_eval(messages[2]) == ()
304+
assert sorted(ast.literal_eval(messages[4])) == [1, 2]
345305

346306
assert not result.errors
347307
result = to_std_dicts(result.data)
@@ -450,31 +410,12 @@ async def test_many_to_many(session_factory):
450410
assert len(sql_statements) == 1
451411
return
452412

453-
assert messages == [
454-
'BEGIN (implicit)',
455-
456-
'SELECT (SELECT CAST(count(reporters.id) AS INTEGER) AS anon_2 \nFROM reporters) AS anon_1, '
457-
'reporters.id AS reporters_id, '
458-
'reporters.first_name AS reporters_first_name, '
459-
'reporters.last_name AS reporters_last_name, '
460-
'reporters.email AS reporters_email, '
461-
'reporters.favorite_pet_kind AS reporters_favorite_pet_kind \n'
462-
'FROM reporters',
463-
'()',
464-
465-
'SELECT reporters_1.id AS reporters_1_id, '
466-
'pets.id AS pets_id, '
467-
'pets.name AS pets_name, '
468-
'pets.pet_kind AS pets_pet_kind, '
469-
'pets.hair_kind AS pets_hair_kind, '
470-
'pets.reporter_id AS pets_reporter_id \n'
471-
'FROM reporters AS reporters_1 '
472-
'JOIN association AS association_1 ON reporters_1.id = association_1.reporter_id '
473-
'JOIN pets ON pets.id = association_1.pet_id \n'
474-
'WHERE reporters_1.id IN (?, ?) '
475-
'ORDER BY pets.id',
476-
'(1, 2)'
477-
]
413+
if not is_sqlalchemy_version_less_than('1.4'):
414+
messages[2] = remove_cache_miss_stat(messages[2])
415+
messages[4] = remove_cache_miss_stat(messages[4])
416+
417+
assert ast.literal_eval(messages[2]) == ()
418+
assert sorted(ast.literal_eval(messages[4])) == [1, 2]
478419

479420
assert not result.errors
480421
result = to_std_dicts(result.data)

graphene_sqlalchemy/tests/test_benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from graphene import relay
55

66
from ..types import SQLAlchemyObjectType
7+
from ..utils import is_sqlalchemy_version_less_than
78
from .models import Article, HairKind, Pet, Reporter
8-
from .utils import is_sqlalchemy_version_less_than
99

1010
if is_sqlalchemy_version_less_than('1.2'):
1111
pytest.skip('SQL batching only works for SQLAlchemy 1.2+', allow_module_level=True)

graphene_sqlalchemy/tests/test_converter.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,11 @@ class Model(declarative_base()):
4747
return convert_sqlalchemy_column(column_prop, get_global_registry(), mock_resolver)
4848

4949

50-
def _test_should_unknown_sqlalchemy_field_raise_exception():
51-
# TODO: SQLALchemy does not export types.Binary, remove or update this test
50+
def test_should_unknown_sqlalchemy_field_raise_exception():
5251
re_err = "Don't know how to convert the SQLAlchemy field"
5352
with pytest.raises(Exception, match=re_err):
5453
# support legacy Binary type and subsequent LargeBinary
55-
get_field(getattr(types, 'LargeBinary', types.Binary)())
54+
get_field(getattr(types, 'LargeBinary', types.BINARY)())
5655

5756

5857
def test_should_date_convert_string():

graphene_sqlalchemy/tests/utils.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import pkg_resources
1+
import re
22

33

44
def to_std_dicts(value):
@@ -11,6 +11,7 @@ def to_std_dicts(value):
1111
return value
1212

1313

14-
def is_sqlalchemy_version_less_than(version_string):
15-
"""Check the installed SQLAlchemy version"""
16-
return pkg_resources.get_distribution('SQLAlchemy').parsed_version < pkg_resources.parse_version(version_string)
14+
def remove_cache_miss_stat(message):
15+
"""Remove the stat from the echoed query message when the cache is missed for sqlalchemy version >= 1.4"""
16+
# https://github.com/sqlalchemy/sqlalchemy/blob/990eb3d8813369d3b8a7776ae85fb33627443d30/lib/sqlalchemy/engine/default.py#L1177
17+
return re.sub(r"\[generated in \d+.?\d*s\]\s", "", message)

graphene_sqlalchemy/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
import warnings
33

4+
import pkg_resources
45
from sqlalchemy.exc import ArgumentError
56
from sqlalchemy.orm import class_mapper, object_mapper
67
from sqlalchemy.orm.exc import UnmappedClassError, UnmappedInstanceError
@@ -140,3 +141,8 @@ def sort_argument_for_model(cls, has_default=True):
140141
enum.default = None
141142

142143
return Argument(List(enum), default_value=enum.default)
144+
145+
146+
def is_sqlalchemy_version_less_than(version_string):
147+
"""Check the installed SQLAlchemy version"""
148+
return pkg_resources.get_distribution('SQLAlchemy').parsed_version < pkg_resources.parse_version(version_string)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# To keep things simple, we only support newer versions of Graphene
1616
"graphene>=3.0.0b7",
1717
"promise>=2.3",
18-
"SQLAlchemy>=1.1,<1.4",
18+
"SQLAlchemy>=1.1,<2",
1919
"aiodataloader>=0.2.0,<1.0",
2020
]
2121

tox.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = pre-commit,py{36,37,38}-sql{11,12,13,14}
2+
envlist = pre-commit,py{36,37,38,39}-sql{11,12,13,14}
33
skipsdist = true
44
minversion = 3.7.0
55

@@ -16,6 +16,7 @@ python =
1616
SQLALCHEMY =
1717
1.2: sql12
1818
1.3: sql13
19+
1.4: sql14
1920

2021
[testenv]
2122
passenv = GITHUB_*

0 commit comments

Comments
 (0)