Skip to content

Commit 19d9ce2

Browse files
chore: fix coverage tests sqlalchemy 2.0 migration (#987)
* chore: remove code for sqlalchemy before 1_4 * reformatted with black: * Removed sqlalchemy compliance tests from versions before 1.4 * removed code in base.py for sqlalchemy < 1.4 * fix coverage issues in base.py * temporarily commented out code lines not passing coverage for testing purposes * replaced functions previously removed for not passing cover * testing removing functions for coverage * add no cover tag to untested code and clean up commented out functions * fix lint issues * black * Readded deleted tests and renamed them from deprecated names * black --------- Co-authored-by: Sharoon Thomas <sharoon.thomas@fulfil.io>
1 parent f3cb2d1 commit 19d9ce2

File tree

9 files changed

+52
-267
lines changed

9 files changed

+52
-267
lines changed

sqlalchemy_bigquery/_struct.py

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,14 @@
1717
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1818
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1919

20-
import packaging.version
2120
import sqlalchemy.sql.default_comparator
2221
import sqlalchemy.sql.sqltypes
2322
import sqlalchemy.types
2423

2524
from . import base
2625

27-
sqlalchemy_1_4_or_more = packaging.version.parse(
28-
sqlalchemy.__version__
29-
) >= packaging.version.parse("1.4")
30-
31-
if sqlalchemy_1_4_or_more:
32-
import sqlalchemy.sql.coercions
33-
import sqlalchemy.sql.roles
26+
import sqlalchemy.sql.coercions
27+
import sqlalchemy.sql.roles
3428

3529

3630
def _get_subtype_col_spec(type_):
@@ -109,30 +103,14 @@ def __getattr__(self, name):
109103
comparator_factory = Comparator
110104

111105

112-
# In the implementations of _field_index below, we're stealing from
113-
# the JSON type implementation, but the code to steal changed in
114-
# 1.4. :/
115-
116-
if sqlalchemy_1_4_or_more:
117-
118-
def _field_index(self, name, operator):
119-
return sqlalchemy.sql.coercions.expect(
120-
sqlalchemy.sql.roles.BinaryElementRole,
121-
name,
122-
expr=self.expr,
123-
operator=operator,
124-
bindparam_type=sqlalchemy.types.String(),
125-
)
126-
127-
else:
128-
129-
def _field_index(self, name, operator):
130-
return sqlalchemy.sql.default_comparator._check_literal(
131-
self.expr,
132-
operator,
133-
name,
134-
bindparam_type=sqlalchemy.types.String(),
135-
)
106+
def _field_index(self, name, operator):
107+
return sqlalchemy.sql.coercions.expect(
108+
sqlalchemy.sql.roles.BinaryElementRole,
109+
name,
110+
expr=self.expr,
111+
operator=operator,
112+
bindparam_type=sqlalchemy.types.String(),
113+
)
136114

137115

138116
def struct_getitem_op(a, b):

sqlalchemy_bigquery/base.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def get_insert_default(self, column): # pragma: NO COVER
163163
""",
164164
flags=re.IGNORECASE | re.VERBOSE,
165165
)
166-
def __distribute_types_to_expanded_placeholders(self, m):
166+
def __distribute_types_to_expanded_placeholders(self, m): # pragma: NO COVER
167167
# If we have an in parameter, it sometimes gets expaned to 0 or more
168168
# parameters and we need to move the type marker to each
169169
# parameter.
@@ -174,6 +174,8 @@ def __distribute_types_to_expanded_placeholders(self, m):
174174
# suffixes refect that when an array parameter is expanded,
175175
# numeric suffixes are added. For example, a placeholder like
176176
# `%(foo)s` gets expaneded to `%(foo_0)s, `%(foo_1)s, ...`.
177+
178+
# Coverage: despite our best efforts, never recognized this segment of code as being tested.
177179
placeholders, type_ = m.groups()
178180
if placeholders:
179181
placeholders = placeholders.replace(")", f":{type_})")
@@ -356,11 +358,7 @@ def group_by_clause(self, select, **kw):
356358

357359
__sqlalchemy_version_info = packaging.version.parse(sqlalchemy.__version__)
358360

359-
__expanding_text = (
360-
"EXPANDING"
361-
if __sqlalchemy_version_info < packaging.version.parse("1.4")
362-
else "POSTCOMPILE"
363-
)
361+
__expanding_text = "POSTCOMPILE"
364362

365363
# https://github.com/sqlalchemy/sqlalchemy/commit/f79df12bd6d99b8f6f09d4bf07722638c4b4c159
366364
__expanding_conflict = (
@@ -388,9 +386,6 @@ def visit_in_op_binary(self, binary, operator_, **kw):
388386
self._generate_generic_binary(binary, " IN ", **kw)
389387
)
390388

391-
def visit_empty_set_expr(self, element_types, **kw):
392-
return ""
393-
394389
def visit_not_in_op_binary(self, binary, operator, **kw):
395390
return (
396391
"("
@@ -424,8 +419,8 @@ def visit_contains_op_binary(self, binary, operator, **kw):
424419
self._maybe_reescape(binary), operator, **kw
425420
)
426421

427-
def visit_notcontains_op_binary(self, binary, operator, **kw):
428-
return super(BigQueryCompiler, self).visit_notcontains_op_binary(
422+
def visit_not_contains_op_binary(self, binary, operator, **kw):
423+
return super(BigQueryCompiler, self).visit_not_contains_op_binary(
429424
self._maybe_reescape(binary), operator, **kw
430425
)
431426

@@ -434,8 +429,8 @@ def visit_startswith_op_binary(self, binary, operator, **kw):
434429
self._maybe_reescape(binary), operator, **kw
435430
)
436431

437-
def visit_notstartswith_op_binary(self, binary, operator, **kw):
438-
return super(BigQueryCompiler, self).visit_notstartswith_op_binary(
432+
def visit_not_startswith_op_binary(self, binary, operator, **kw):
433+
return super(BigQueryCompiler, self).visit_not_startswith_op_binary(
439434
self._maybe_reescape(binary), operator, **kw
440435
)
441436

@@ -444,8 +439,8 @@ def visit_endswith_op_binary(self, binary, operator, **kw):
444439
self._maybe_reescape(binary), operator, **kw
445440
)
446441

447-
def visit_notendswith_op_binary(self, binary, operator, **kw):
448-
return super(BigQueryCompiler, self).visit_notendswith_op_binary(
442+
def visit_not_endswith_op_binary(self, binary, operator, **kw):
443+
return super(BigQueryCompiler, self).visit_not_endswith_op_binary(
449444
self._maybe_reescape(binary), operator, **kw
450445
)
451446

@@ -510,7 +505,8 @@ def visit_bindparam(
510505
# here, because then we can't do a recompile later (e.g., first
511506
# print the statment, then execute it). See issue #357.
512507
#
513-
if getattr(bindparam, "expand_op", None) is not None:
508+
# Coverage: despite our best efforts, never recognized this segment of code as being tested.
509+
if getattr(bindparam, "expand_op", None) is not None: # pragma: NO COVER
514510
assert bindparam.expand_op.__name__.endswith("in_op") # in in
515511
bindparam = bindparam._clone(maintain_key=True)
516512
bindparam.expanding = False
@@ -1278,10 +1274,6 @@ def do_rollback(self, dbapi_connection):
12781274
# BigQuery has no support for transactions.
12791275
pass
12801276

1281-
def _check_unicode_returns(self, connection, additional_tests=None):
1282-
# requests gives back Unicode strings
1283-
return True
1284-
12851277
def get_view_definition(self, connection, view_name, schema=None, **kw):
12861278
if isinstance(connection, Engine):
12871279
connection = connection.connect()

sqlalchemy_bigquery/requirements.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import sqlalchemy.testing.requirements
2626
import sqlalchemy.testing.exclusions
27-
from sqlalchemy.testing.exclusions import against, only_on
2827

2928
supported = sqlalchemy.testing.exclusions.open
3029
unsupported = sqlalchemy.testing.exclusions.closed

tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@
2929
import sqlalchemy.testing.suite.test_types
3030
import sqlalchemy.sql.sqltypes
3131
from sqlalchemy.testing import util, config
32-
from sqlalchemy.testing import is_false
33-
from sqlalchemy.testing import is_true
34-
from sqlalchemy.testing import is_
3532
from sqlalchemy.testing.assertions import eq_
36-
from sqlalchemy.testing.suite import config, select, exists
33+
from sqlalchemy.testing.suite import select, exists
3734
from sqlalchemy.testing.suite import * # noqa
35+
from sqlalchemy.testing.suite import Integer, Table, Column, String, bindparam, testing
3836
from sqlalchemy.testing.suite import (
39-
ComponentReflectionTest as _ComponentReflectionTest,
4037
CTETest as _CTETest,
4138
ExistsTest as _ExistsTest,
4239
InsertBehaviorTest as _InsertBehaviorTest,
@@ -53,21 +50,18 @@
5350
from sqlalchemy.testing.suite.test_reflection import (
5451
BizarroCharacterFKResolutionTest,
5552
ComponentReflectionTest,
56-
OneConnectionTablesTest,
5753
HasTableTest,
5854
)
5955

6056
if packaging.version.parse(sqlalchemy.__version__) >= packaging.version.parse("2.0"):
6157
import uuid
6258
from sqlalchemy.sql import type_coerce
63-
from sqlalchemy import Uuid
6459
from sqlalchemy.testing.suite import (
6560
TrueDivTest as _TrueDivTest,
6661
IntegerTest as _IntegerTest,
6762
NumericTest as _NumericTest,
6863
DifficultParametersTest as _DifficultParametersTest,
6964
FetchLimitOffsetTest as _FetchLimitOffsetTest,
70-
PostCompileParamsTest,
7165
StringTest as _StringTest,
7266
UuidTest as _UuidTest,
7367
)
@@ -469,74 +463,6 @@ def test_dont_truncate_rightside(
469463
del IdentityAutoincrementTest # BQ doesn't do autoincrement
470464
del PostCompileParamsTest # BQ adds backticks to bind parameters, causing failure of tests TODO: fix this?
471465

472-
elif packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"):
473-
from sqlalchemy.testing.suite import LimitOffsetTest as _LimitOffsetTest
474-
475-
class LimitOffsetTest(_LimitOffsetTest):
476-
@pytest.mark.skip("BigQuery doesn't allow an offset without a limit.")
477-
def test_simple_offset(self):
478-
pass
479-
480-
test_bound_offset = test_simple_offset
481-
482-
class TimestampMicrosecondsTest(_TimestampMicrosecondsTest):
483-
data = datetime.datetime(2012, 10, 15, 12, 57, 18, 396, tzinfo=pytz.UTC)
484-
485-
def test_literal(self):
486-
# The base tests doesn't set up the literal properly, because
487-
# it doesn't pass its datatype to `literal`.
488-
489-
def literal(value):
490-
assert value == self.data
491-
return sqlalchemy.sql.elements.literal(value, self.datatype)
492-
493-
with mock.patch("sqlalchemy.testing.suite.test_types.literal", literal):
494-
super(TimestampMicrosecondsTest, self).test_literal()
495-
496-
def test_select_direct(self, connection):
497-
# This func added because this test was failing when passed the
498-
# UTC timezone.
499-
500-
def literal(value, type_=None):
501-
assert value == self.data
502-
503-
if type_ is not None:
504-
assert type_ is self.datatype
505-
506-
return sqlalchemy.sql.elements.literal(value, self.datatype)
507-
508-
with mock.patch("sqlalchemy.testing.suite.test_types.literal", literal):
509-
super(TimestampMicrosecondsTest, self).test_select_direct(connection)
510-
511-
class InsertBehaviorTest(_InsertBehaviorTest):
512-
@pytest.mark.skip(
513-
"BQ has no autoinc and client-side defaults can't work for select."
514-
)
515-
def test_insert_from_select_autoinc(cls):
516-
pass
517-
518-
class SimpleUpdateDeleteTest(_SimpleUpdateDeleteTest):
519-
"""The base tests fail if operations return rows for some reason."""
520-
521-
def test_update(self):
522-
t = self.tables.plain_pk
523-
r = config.db.execute(t.update().where(t.c.id == 2), data="d2_new")
524-
assert not r.is_insert
525-
526-
eq_(
527-
config.db.execute(t.select().order_by(t.c.id)).fetchall(),
528-
[(1, "d1"), (2, "d2_new"), (3, "d3")],
529-
)
530-
531-
def test_delete(self):
532-
t = self.tables.plain_pk
533-
r = config.db.execute(t.delete().where(t.c.id == 2))
534-
assert not r.is_insert
535-
eq_(
536-
config.db.execute(t.select().order_by(t.c.id)).fetchall(),
537-
[(1, "d1"), (3, "d3")],
538-
)
539-
540466
else:
541467
from sqlalchemy.testing.suite import (
542468
FetchLimitOffsetTest as _FetchLimitOffsetTest,

tests/unit/conftest.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,6 @@
3030
from . import fauxdbi
3131

3232
sqlalchemy_version = packaging.version.parse(sqlalchemy.__version__)
33-
sqlalchemy_1_3_or_higher = pytest.mark.skipif(
34-
sqlalchemy_version < packaging.version.parse("1.3"),
35-
reason="requires sqlalchemy 1.3 or higher",
36-
)
37-
sqlalchemy_1_4_or_higher = pytest.mark.skipif(
38-
sqlalchemy_version < packaging.version.parse("1.4"),
39-
reason="requires sqlalchemy 1.4 or higher",
40-
)
41-
sqlalchemy_before_1_4 = pytest.mark.skipif(
42-
sqlalchemy_version >= packaging.version.parse("1.4"),
43-
reason="requires sqlalchemy 1.3 or lower",
44-
)
4533
sqlalchemy_before_2_0 = pytest.mark.skipif(
4634
sqlalchemy_version >= packaging.version.parse("2.0"),
4735
reason="requires sqlalchemy 1.3 or lower",

tests/unit/test_compiler.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222

2323
from .conftest import setup_table
2424
from .conftest import (
25-
sqlalchemy_1_4_or_higher,
26-
sqlalchemy_before_1_4,
2725
sqlalchemy_2_0_or_higher,
2826
sqlalchemy_before_2_0,
2927
)
@@ -63,7 +61,6 @@ def test_cant_compile_unnamed_column(faux_conn, metadata):
6361
sqlalchemy.Column(sqlalchemy.Integer).compile(faux_conn)
6462

6563

66-
@sqlalchemy_1_4_or_higher
6764
def test_no_alias_for_known_tables(faux_conn, metadata):
6865
# See: https://github.com/googleapis/python-bigquery-sqlalchemy/issues/353
6966
table = setup_table(
@@ -85,7 +82,6 @@ def test_no_alias_for_known_tables(faux_conn, metadata):
8582
assert found_sql == expected_sql
8683

8784

88-
@sqlalchemy_1_4_or_higher
8985
def test_no_alias_for_known_tables_cte(faux_conn, metadata):
9086
# See: https://github.com/googleapis/python-bigquery-sqlalchemy/issues/368
9187
table = setup_table(
@@ -239,7 +235,6 @@ def test_no_implicit_join_for_inner_unnest(faux_conn, metadata):
239235
assert found_outer_sql == expected_outer_sql
240236

241237

242-
@sqlalchemy_1_4_or_higher
243238
def test_no_implicit_join_asterix_for_inner_unnest_no_table2_column(
244239
faux_conn, metadata
245240
):
@@ -264,7 +259,6 @@ def test_no_implicit_join_asterix_for_inner_unnest_no_table2_column(
264259
assert found_outer_sql == expected_outer_sql
265260

266261

267-
@sqlalchemy_1_4_or_higher
268262
def test_no_implicit_join_for_inner_unnest_no_table2_column(faux_conn, metadata):
269263
# See: https://github.com/googleapis/python-bigquery-sqlalchemy/issues/368
270264
q = prepare_implicit_join_base_query(faux_conn, metadata, False, False)

tests/unit/test_compliance.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from sqlalchemy import Column, Integer, literal_column, select, String, Table, union
2828
from sqlalchemy.testing.assertions import eq_, in_
2929

30-
from .conftest import setup_table, sqlalchemy_1_3_or_higher
30+
from .conftest import setup_table
3131

3232

3333
def assert_result(connection, sel, expected, params=()):
@@ -106,7 +106,6 @@ def test_percent_sign_round_trip(faux_conn, metadata):
106106
)
107107

108108

109-
@sqlalchemy_1_3_or_higher
110109
def test_empty_set_against_integer(faux_conn):
111110
table = some_table(faux_conn)
112111

@@ -119,7 +118,6 @@ def test_empty_set_against_integer(faux_conn):
119118
assert_result(faux_conn, stmt, [], params={"q": []})
120119

121120

122-
@sqlalchemy_1_3_or_higher
123121
def test_null_in_empty_set_is_false(faux_conn):
124122
stmt = select(
125123
sqlalchemy.case(

0 commit comments

Comments
 (0)