From 59e7c3f2cb5ca8381a8674eb3f2aef59c37e9fa6 Mon Sep 17 00:00:00 2001 From: MF2199 <38331387+mf2199@users.noreply.github.com> Date: Mon, 31 Aug 2020 15:55:01 -0400 Subject: [PATCH] feat: Stage 3-4 of `nox` implementation - adding auto-format targets (#478) --- django_spanner/base.py | 4 +- django_spanner/compiler.py | 8 +-- django_spanner/introspection.py | 10 +-- django_spanner/operations.py | 9 +-- django_spanner/schema.py | 4 +- django_spanner/utils.py | 2 +- docs/conf.py | 5 +- noxfile.py | 48 +++++++++++++- setup.cfg | 9 +++ setup.py | 57 ++++++++-------- spanner_dbapi/connection.py | 2 +- spanner_dbapi/cursor.py | 8 +-- spanner_dbapi/parse_utils.py | 2 +- tests/spanner_dbapi/test_connect.py | 22 +++++-- tests/spanner_dbapi/test_parse_utils.py | 86 ++++++++++++------------- tests/spanner_dbapi/test_parser.py | 6 +- tests/spanner_dbapi/test_types.py | 4 +- tests/spanner_dbapi/test_utils.py | 4 +- tests/spanner_dbapi/test_version.py | 1 - tox.ini | 0 20 files changed, 168 insertions(+), 123 deletions(-) create mode 100644 tox.ini diff --git a/django_spanner/base.py b/django_spanner/base.py index 3cf9c47e15..c63e7dfe54 100644 --- a/django_spanner/base.py +++ b/django_spanner/base.py @@ -81,9 +81,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): # expression or the result of a bilateral transformation). In those cases, # special characters for REGEXP_CONTAINS operators (e.g. \, *, _) must be # escaped on database side. - pattern_esc = ( - r'REPLACE(REPLACE(REPLACE({}, "\\", "\\\\"), "%%", r"\%%"), "_", r"\_")' - ) + pattern_esc = r'REPLACE(REPLACE(REPLACE({}, "\\", "\\\\"), "%%", r"\%%"), "_", r"\_")' # These are all no-ops in favor of using REGEXP_CONTAINS in the customized # lookups. pattern_ops = { diff --git a/django_spanner/compiler.py b/django_spanner/compiler.py index da8bf889b5..202ef103dc 100644 --- a/django_spanner/compiler.py +++ b/django_spanner/compiler.py @@ -7,15 +7,9 @@ from django.core.exceptions import EmptyResultSet from django.db.models.sql.compiler import ( SQLAggregateCompiler as BaseSQLAggregateCompiler, -) -from django.db.models.sql.compiler import SQLCompiler as BaseSQLCompiler -from django.db.models.sql.compiler import ( + SQLCompiler as BaseSQLCompiler, SQLDeleteCompiler as BaseSQLDeleteCompiler, -) -from django.db.models.sql.compiler import ( SQLInsertCompiler as BaseSQLInsertCompiler, -) -from django.db.models.sql.compiler import ( SQLUpdateCompiler as BaseSQLUpdateCompiler, ) from django.db.utils import DatabaseError diff --git a/django_spanner/introspection.py b/django_spanner/introspection.py index f233b453ed..ab9d29aa3f 100644 --- a/django_spanner/introspection.py +++ b/django_spanner/introspection.py @@ -95,7 +95,7 @@ def get_relations(self, cursor, table_name): rc.UNIQUE_CONSTRAINT_NAME = ccu.CONSTRAINT_NAME WHERE tc.TABLE_NAME="%s"''' - % self.connection.ops.quote_name(table_name), + % self.connection.ops.quote_name(table_name) ) return { column: (referred_column, referred_table) @@ -116,7 +116,7 @@ def get_primary_key_column(self, cursor, table_name): WHERE tc.TABLE_NAME="%s" AND tc.CONSTRAINT_TYPE='PRIMARY KEY' AND tc.TABLE_SCHEMA='' """ - % self.connection.ops.quote_name(table_name), + % self.connection.ops.quote_name(table_name) ) return results[0][0] if results else None @@ -133,7 +133,7 @@ def get_constraints(self, cursor, table_name): INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME="{table}"'''.format( table=quoted_table_name - ), + ) ) for constraint, column_name in constraint_columns: if constraint not in constraints: @@ -160,7 +160,7 @@ def get_constraints(self, cursor, table_name): WHERE TABLE_NAME="{table}"'''.format( table=quoted_table_name - ), + ) ) for constraint, constraint_type in constraint_types: already_added = constraint in constraints @@ -208,7 +208,7 @@ def get_constraints(self, cursor, table_name): idx_col.ORDINAL_POSITION """.format( table=quoted_table_name - ), + ) ) for ( index_name, diff --git a/django_spanner/operations.py b/django_spanner/operations.py index c994646bb1..8aee6bcc12 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -16,15 +16,11 @@ from django.db.utils import DatabaseError from django.utils import timezone from django.utils.duration import duration_microseconds - from spanner_dbapi.parse_utils import DateStr, TimestampStr, escape_name class DatabaseOperations(BaseDatabaseOperations): - cast_data_types = { - "CharField": "STRING", - "TextField": "STRING", - } + cast_data_types = {"CharField": "STRING", "TextField": "STRING"} cast_char_field_without_max_length = "STRING" compiler_module = "django_spanner.compiler" # Django's lookup names that require a different name in Spanner's @@ -247,8 +243,7 @@ def datetime_cast_time_sql(self, field_name, tzname): # TIMESTAMP to another time zone. return ( "TIMESTAMP(FORMAT_TIMESTAMP(" - "'%%Y-%%m-%%d %%R:%%E9S %%Z', %s, '%s'))" - % (field_name, tzname,) + "'%%Y-%%m-%%d %%R:%%E9S %%Z', %s, '%s'))" % (field_name, tzname) ) def date_interval_sql(self, timedelta): diff --git a/django_spanner/schema.py b/django_spanner/schema.py index 2f9e0192c9..c3cafcd0eb 100644 --- a/django_spanner/schema.py +++ b/django_spanner/schema.py @@ -75,7 +75,7 @@ def create_model(self, model): ) # Add the SQL to our big list column_sqls.append( - "%s %s" % (self.quote_name(field.column), definition,) + "%s %s" % (self.quote_name(field.column), definition) ) # Create a unique constraint separately because Spanner doesn't # allow them inline on a column. @@ -279,7 +279,7 @@ def _alter_field( nullability_changed = old_field.null != new_field.null if nullability_changed: index_names = self._constraint_names( - model, [old_field.column], index=True, + model, [old_field.column], index=True ) if index_names and not old_field.db_index: raise NotSupportedError( diff --git a/django_spanner/utils.py b/django_spanner/utils.py index afdcbb0bfc..1136c33a87 100644 --- a/django_spanner/utils.py +++ b/django_spanner/utils.py @@ -15,6 +15,6 @@ def check_django_compatability(): raise ImproperlyConfigured( "You must use the latest version of django-spanner {A}.{B}.x " "with Django {A}.{B}.y (found django-spanner {C}).".format( - A=django.VERSION[0], B=django.VERSION[1], C=__version__, + A=django.VERSION[0], B=django.VERSION[1], C=__version__ ) ) diff --git a/docs/conf.py b/docs/conf.py index fe79f1e098..301495e535 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -350,7 +350,10 @@ intersphinx_mapping = { "python": ("http://python.readthedocs.org/en/latest/", None), "google-auth": ("https://google-auth.readthedocs.io/en/stable", None), - "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None), + "google.api_core": ( + "https://googleapis.dev/python/google-api-core/latest/", + None, + ), "grpc": ("https://grpc.io/grpc/python/", None), } diff --git a/noxfile.py b/noxfile.py index 04b4694086..4570168cd3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -14,6 +14,52 @@ import shutil +BLACK_VERSION = "black==19.10b0" +BLACK_PATHS = [ + "django_spanner", + "docs", + "spanner_dbapi", + "tests", + "noxfile.py", + "setup.py", +] + + +@nox.session(python="3.8") +def lint(session): + """Run linters. + + Returns a failure if the linters find linting errors or sufficiently + serious code quality issues. + """ + session.install("flake8", BLACK_VERSION) + session.run("black", "--check", *BLACK_PATHS) + session.run("flake8", "django_spanner", "spanner_dbapi", "tests") + + +@nox.session(python="3.8") +def blacken(session): + """Run black. + + Format code to uniform standard. + + This currently uses Python 3.6 due to the automated Kokoro run of synthtool. + That run uses an image that doesn't have 3.6 installed. Before updating this + check the state of the `gcp_ubuntu_config` we use for that Kokoro run. + """ + session.install(BLACK_VERSION) + session.run("black", *BLACK_PATHS) + + +@nox.session(python="3.8") +def lint_setup_py(session): + """Verify that setup.py is valid (including RST check).""" + session.install("docutils", "pygments") + session.run( + "python", "setup.py", "check", "--restructuredtext", "--strict" + ) + + def default(session): # Install all test dependencies, then install this package in-place. session.install("mock", "pytest", "pytest-cov") @@ -24,7 +70,7 @@ def default(session): "py.test", "--quiet", os.path.join("tests", "spanner_dbapi"), - *session.posargs, + *session.posargs ) diff --git a/setup.cfg b/setup.cfg index 18b71e0e53..f53a9ca616 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,15 @@ ignore = W503 # allow line breaks before binary ops W504 # allow line breaks after binary ops E203 # allow whitespace before ':' (https://github.com/psf/black#slices) +exclude = + # Exclude generated code. + **/_build/** + + # Standard linting exemptions. + __pycache__, + .git, + *.pyc, + conf.py [isort] use_parentheses = True diff --git a/setup.py b/setup.py index d5515b05d5..b8578f2c32 100644 --- a/setup.py +++ b/setup.py @@ -19,10 +19,7 @@ # 'Development Status :: 4 - Beta' # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 3 - Alpha" -dependencies = [ - 'sqlparse >= 0.3.0', - 'google-cloud-spanner >= 1.8.0', -] +dependencies = ["sqlparse >= 0.3.0", "google-cloud-spanner >= 1.8.0"] extras = {} @@ -36,30 +33,30 @@ setup( - name=name, - version=version, - description=description, - long_description=readme, - author='Google LLC', - author_email='cloud-spanner-developers@googlegroups.com', - license='BSD', - packages=find_packages(exclude=['tests']), - install_requires=dependencies, - url="https://github.com/googleapis/python-spanner-django", - classifiers=[ - release_status, - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Utilities', - 'Framework :: Django', - 'Framework :: Django :: 2.2', - ], - extras_require=extras, - python_requires=">=3.5", + name=name, + version=version, + description=description, + long_description=readme, + author="Google LLC", + author_email="cloud-spanner-developers@googlegroups.com", + license="BSD", + packages=find_packages(exclude=["tests"]), + install_requires=dependencies, + url="https://github.com/googleapis/python-spanner-django", + classifiers=[ + release_status, + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Utilities", + "Framework :: Django", + "Framework :: Django :: 2.2", + ], + extras_require=extras, + python_requires=">=3.5", ) diff --git a/spanner_dbapi/connection.py b/spanner_dbapi/connection.py index 10e76576f8..1abc68519f 100644 --- a/spanner_dbapi/connection.py +++ b/spanner_dbapi/connection.py @@ -108,7 +108,7 @@ def get_table_column_schema(self, table_name): column_details = {} for column_name, is_nullable, spanner_type in rows: column_details[column_name] = ColumnDetails( - null_ok=is_nullable == "YES", spanner_type=spanner_type, + null_ok=is_nullable == "YES", spanner_type=spanner_type ) return column_details diff --git a/spanner_dbapi/cursor.py b/spanner_dbapi/cursor.py index 3a4a6b938e..4dcc69df77 100644 --- a/spanner_dbapi/cursor.py +++ b/spanner_dbapi/cursor.py @@ -104,9 +104,7 @@ def execute(self, sql, args=None): raise OperationalError(e.details if hasattr(e, "details") else e) def __handle_update(self, sql, params): - self._connection.in_transaction( - self.__do_execute_update, sql, params, - ) + self._connection.in_transaction(self.__do_execute_update, sql, params) def __do_execute_update(self, transaction, sql, params, param_types=None): sql = ensure_where_clause(sql) @@ -141,14 +139,14 @@ def __handle_insert(self, sql, params): # The common case of multiple values being passed in # non-complex pyformat args and need to be uploaded in one RPC. return self._connection.in_transaction( - self.__do_execute_insert_homogenous, parts, + self.__do_execute_insert_homogenous, parts ) else: # All the other cases that are esoteric and need # transaction.execute_sql sql_params_list = parts.get("sql_params_list") return self._connection.in_transaction( - self.__do_execute_insert_heterogenous, sql_params_list, + self.__do_execute_insert_heterogenous, sql_params_list ) def __do_execute_insert_heterogenous(self, transaction, sql_params_list): diff --git a/spanner_dbapi/parse_utils.py b/spanner_dbapi/parse_utils.py index ce1f0ebe27..edf3d3e64f 100644 --- a/spanner_dbapi/parse_utils.py +++ b/spanner_dbapi/parse_utils.py @@ -299,7 +299,7 @@ def rows_for_insert_or_update(columns, params, pyformat_args=None): # Now chop up the strides. strides = [] for step in range(0, len(params), n_stride): - stride = tuple(params[step: step + n_stride:]) + stride = tuple(params[step : step + n_stride :]) strides.append(stride) return strides diff --git a/tests/spanner_dbapi/test_connect.py b/tests/spanner_dbapi/test_connect.py index 64eaf8cd7d..716df619fb 100644 --- a/tests/spanner_dbapi/test_connect.py +++ b/tests/spanner_dbapi/test_connect.py @@ -36,19 +36,26 @@ def test_connect(self): ) as client_info_mock: connection = connect( - "test-instance", "test-database", PROJECT, CREDENTIALS, USER_AGENT + "test-instance", + "test-database", + PROJECT, + CREDENTIALS, + USER_AGENT, ) self.assertIsInstance(connection, Connection) client_info_mock.assert_called_once_with(USER_AGENT) client_mock.assert_called_once_with( - project=PROJECT, credentials=CREDENTIALS, client_info=CLIENT_INFO + project=PROJECT, + credentials=CREDENTIALS, + client_info=CLIENT_INFO, ) def test_instance_not_found(self): with mock.patch( - "google.cloud.spanner_v1.instance.Instance.exists", return_value=False + "google.cloud.spanner_v1.instance.Instance.exists", + return_value=False, ) as exists_mock: with self.assertRaises(ValueError): @@ -58,10 +65,12 @@ def test_instance_not_found(self): def test_database_not_found(self): with mock.patch( - "google.cloud.spanner_v1.instance.Instance.exists", return_value=True + "google.cloud.spanner_v1.instance.Instance.exists", + return_value=True, ): with mock.patch( - "google.cloud.spanner_v1.database.Database.exists", return_value=False + "google.cloud.spanner_v1.database.Database.exists", + return_value=False, ) as exists_mock: with self.assertRaises(ValueError): @@ -88,7 +97,8 @@ def test_connect_database_id(self): "google.cloud.spanner_v1.instance.Instance.database" ) as database_mock: with mock.patch( - "google.cloud.spanner_v1.instance.Instance.exists", return_value=True + "google.cloud.spanner_v1.instance.Instance.exists", + return_value=True, ): connection = connect("test-instance", DATABASE) diff --git a/tests/spanner_dbapi/test_parse_utils.py b/tests/spanner_dbapi/test_parse_utils.py index e1593a0686..91d795ad0f 100644 --- a/tests/spanner_dbapi/test_parse_utils.py +++ b/tests/spanner_dbapi/test_parse_utils.py @@ -9,7 +9,6 @@ from unittest import TestCase from google.cloud.spanner_v1 import param_types - from spanner_dbapi.exceptions import Error, ProgrammingError from spanner_dbapi.parse_utils import ( STMT_DDL, @@ -31,8 +30,8 @@ class ParseUtilsTests(TestCase): def test_classify_stmt(self): cases = [ - ("SELECT 1", STMT_NON_UPDATING,), - ("SELECT s.SongName FROM Songs AS s", STMT_NON_UPDATING,), + ("SELECT 1", STMT_NON_UPDATING), + ("SELECT s.SongName FROM Songs AS s", STMT_NON_UPDATING), ( "WITH sq AS (SELECT SchoolID FROM Roster) SELECT * from sq", STMT_NON_UPDATING, @@ -47,7 +46,7 @@ def test_classify_stmt(self): "Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums", STMT_DDL, ), - ("CREATE INDEX SongsBySongName ON Songs(SongName)", STMT_DDL,), + ("CREATE INDEX SongsBySongName ON Songs(SongName)", STMT_DDL), ( "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)", STMT_DDL, @@ -72,13 +71,13 @@ def test_parse_insert(self): "sql_params_list": [ ( "INSERT INTO django_migrations (app, name, applied) VALUES (%s, %s, %s)", - (1, 2, 3,), + (1, 2, 3), ), ( "INSERT INTO django_migrations (app, name, applied) VALUES (%s, %s, %s)", - (4, 5, 6,), + (4, 5, 6), ), - ], + ] }, ), ( @@ -88,13 +87,13 @@ def test_parse_insert(self): "sql_params_list": [ ( "INSERT INTO django_migrations (app, name, applied) VALUES (%s, %s, %s)", - (1, 2, 3,), + (1, 2, 3), ), ( "INSERT INTO django_migrations (app, name, applied) VALUES (%s, %s, %s)", - (4, 5, 6,), + (4, 5, 6), ), - ], + ] }, ), ( @@ -110,7 +109,7 @@ def test_parse_insert(self): "ORDER BY first_name, last_name", None, ) - ], + ] }, ), ( @@ -121,17 +120,17 @@ def test_parse_insert(self): "sql_params_list": [ ( "INSERT INTO ap (n, ct, cn) VALUES (%s, %s, %s)", - (1, 2, 3,), + (1, 2, 3), ), ( "INSERT INTO ap (n, ct, cn) VALUES (%s, %s, %s)", - (4, 5, 6,), + (4, 5, 6), ), ( "INSERT INTO ap (n, ct, cn) VALUES (%s, %s, %s)", - (7, 8, 9,), + (7, 8, 9), ), - ], + ] }, ), ( @@ -139,10 +138,10 @@ def test_parse_insert(self): (1, 4, 5), { "sql_params_list": [ - ("INSERT INTO `no` (`yes`) VALUES (%s)", (1,),), - ("INSERT INTO `no` (`yes`) VALUES (%s)", (4,),), - ("INSERT INTO `no` (`yes`) VALUES (%s)", (5,),), - ], + ("INSERT INTO `no` (`yes`) VALUES (%s)", (1,)), + ("INSERT INTO `no` (`yes`) VALUES (%s)", (4,)), + ("INSERT INTO `no` (`yes`) VALUES (%s)", (5,)), + ] }, ), ( @@ -150,8 +149,8 @@ def test_parse_insert(self): None, { "sql_params_list": [ - ("INSERT INTO T (f1, f2) VALUES (1, 2)", None,), - ], + ("INSERT INTO T (f1, f2) VALUES (1, 2)", None) + ] }, ), ( @@ -161,7 +160,7 @@ def test_parse_insert(self): "sql_params_list": [ ( "INSERT INTO `no` (`yes`, tiff) VALUES(%s, LOWER(%s))", - (1, "FOO",), + (1, "FOO"), ), ( "INSERT INTO `no` (`yes`, tiff) VALUES(%s, %s)", @@ -171,7 +170,7 @@ def test_parse_insert(self): "INSERT INTO `no` (`yes`, tiff) VALUES(%s, %s)", (11, 29), ), - ], + ] }, ), ] @@ -214,41 +213,41 @@ def test_rows_for_insert_or_update(self): cases = [ ( ["id", "app", "name"], - [(5, "ap", "n",), (6, "bp", "m",)], + [(5, "ap", "n"), (6, "bp", "m")], None, - [(5, "ap", "n",), (6, "bp", "m",)], + [(5, "ap", "n"), (6, "bp", "m")], ), ( ["app", "name"], - [("ap", "n",), ("bp", "m",)], + [("ap", "n"), ("bp", "m")], None, - [("ap", "n"), ("bp", "m",)], + [("ap", "n"), ("bp", "m")], ), ( ["app", "name", "fn"], ["ap", "n", "f1", "bp", "m", "f2", "cp", "o", "f3"], ["(%s, %s, %s)", "(%s, %s, %s)", "(%s, %s, %s)"], - [("ap", "n", "f1",), ("bp", "m", "f2",), ("cp", "o", "f3",)], + [("ap", "n", "f1"), ("bp", "m", "f2"), ("cp", "o", "f3")], ), ( ["app", "name", "fn", "ln"], [ - ("ap", "n", (45, "nested",), "ll",), - ("bp", "m", "f2", "mt",), - ("fp", "cp", "o", "f3",), + ("ap", "n", (45, "nested"), "ll"), + ("bp", "m", "f2", "mt"), + ("fp", "cp", "o", "f3"), ], None, [ - ("ap", "n", (45, "nested",), "ll",), - ("bp", "m", "f2", "mt",), - ("fp", "cp", "o", "f3",), + ("ap", "n", (45, "nested"), "ll"), + ("bp", "m", "f2", "mt"), + ("fp", "cp", "o", "f3"), ], ), ( ["app", "name", "fn"], ["ap", "n", "f1"], None, - [("ap", "n", "f1",)], + [("ap", "n", "f1")], ), ] @@ -262,7 +261,7 @@ def test_sql_pyformat_args_to_spanner(self): ( ( "SELECT * from t WHERE f1=%s, f2 = %s, f3=%s", - (10, "abc", "y**$22l3f",), + (10, "abc", "y**$22l3f"), ), ( "SELECT * from t WHERE f1=@a0, f2 = @a1, f3=@a2", @@ -272,7 +271,7 @@ def test_sql_pyformat_args_to_spanner(self): ( ( "INSERT INTO t (f1, f2, f2) VALUES (%s, %s, %s)", - ("app", "name", "applied",), + ("app", "name", "applied"), ), ( "INSERT INTO t (f1, f2, f2) VALUES (@a0, @a1, @a2)", @@ -306,7 +305,7 @@ def test_sql_pyformat_args_to_spanner(self): ( ( "SELECT (an.p + %s) AS np FROM an WHERE (an.p + %s) = %s", - (1, 1.0, decimal.Decimal("31"),), + (1, 1.0, decimal.Decimal("31")), ), ( "SELECT (an.p + @a0) AS np FROM an WHERE (an.p + @a1) = @a2", @@ -329,8 +328,8 @@ def test_sql_pyformat_args_to_spanner_invalid(self): cases = [ ( "SELECT * from t WHERE f1=%s, f2 = %s, f3=%s, extra=%s", - (10, "abc", "y**$22l3f",), - ), + (10, "abc", "y**$22l3f"), + ) ] for sql, params in cases: with self.subTest(sql=sql): @@ -470,7 +469,7 @@ def test_ensure_where_clause(self): "UPDATE T SET r=r*0.9 WHERE id IN (SELECT id FROM items WHERE r / w >= 1.3 AND q > 100)", "UPDATE T SET r=r*0.9 WHERE id IN (SELECT id FROM items WHERE r / w >= 1.3 AND q > 100)", ), - ("DELETE * FROM TABLE", "DELETE * FROM TABLE WHERE 1=1",), + ("DELETE * FROM TABLE", "DELETE * FROM TABLE WHERE 1=1"), ] for sql, want in cases: @@ -493,10 +492,7 @@ def test_escape_name(self): self.assertEqual(got, want) def test_strip_backticks(self): - cases = [ - ("foo", "foo"), - ("`foo`", "foo"), - ] + cases = [("foo", "foo"), ("`foo`", "foo")] for name, want in cases: with self.subTest(name=name): got = strip_backticks(name) diff --git a/tests/spanner_dbapi/test_parser.py b/tests/spanner_dbapi/test_parser.py index d581d2b8be..8e08775a86 100644 --- a/tests/spanner_dbapi/test_parser.py +++ b/tests/spanner_dbapi/test_parser.py @@ -163,7 +163,7 @@ def test_expect_values(self): pyfmt_str, func("LOWER", a_args([pyfmt_str, pyfmt_str])), ] - ), + ) ] ), ), @@ -172,8 +172,8 @@ def test_expect_values(self): "", values( [ - a_args([func("UPPER", a_args([pyfmt_str])),]), - a_args([pyfmt_str,]), + a_args([func("UPPER", a_args([pyfmt_str]))]), + a_args([pyfmt_str]), ] ), ), diff --git a/tests/spanner_dbapi/test_types.py b/tests/spanner_dbapi/test_types.py index 90067f089e..690ec7463f 100644 --- a/tests/spanner_dbapi/test_types.py +++ b/tests/spanner_dbapi/test_types.py @@ -85,8 +85,8 @@ def test_PeekIterator(self): cases = [ ("list", [1, 2, 3, 4, 6, 7], [1, 2, 3, 4, 6, 7]), ("iter_from_list", iter([1, 2, 3, 4, 6, 7]), [1, 2, 3, 4, 6, 7]), - ("tuple", ("a", 12, 0xFF,), ["a", 12, 0xFF]), - ("iter_from_tuple", iter(("a", 12, 0xFF,)), ["a", 12, 0xFF]), + ("tuple", ("a", 12, 0xFF), ["a", 12, 0xFF]), + ("iter_from_tuple", iter(("a", 12, 0xFF)), ["a", 12, 0xFF]), ("no_args", (), []), ] diff --git a/tests/spanner_dbapi/test_utils.py b/tests/spanner_dbapi/test_utils.py index f9135783b8..8ffcd99d2f 100644 --- a/tests/spanner_dbapi/test_utils.py +++ b/tests/spanner_dbapi/test_utils.py @@ -26,10 +26,10 @@ def test_peekIterator_list_rows_converted_to_tuples(self): self.assertEqual(list(seventeen), [(17,)]) pit = PeekIterator([["%", "%d"]]) - self.assertEqual(next(pit), ("%", "%d",)) + self.assertEqual(next(pit), ("%", "%d")) pit = PeekIterator([("Clark", "Kent")]) - self.assertEqual(next(pit), ("Clark", "Kent",)) + self.assertEqual(next(pit), ("Clark", "Kent")) def test_peekIterator_nonlist_rows_unconverted(self): pi = PeekIterator(["a", "b", "c", "d", "e"]) diff --git a/tests/spanner_dbapi/test_version.py b/tests/spanner_dbapi/test_version.py index ec90e8c549..ea2c718a2d 100644 --- a/tests/spanner_dbapi/test_version.py +++ b/tests/spanner_dbapi/test_version.py @@ -8,7 +8,6 @@ from unittest import TestCase from google.api_core.gapic_v1.client_info import ClientInfo - from spanner_dbapi.version import DEFAULT_USER_AGENT, google_client_info vers = sys.version_info diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000..e69de29bb2