Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Stage 3-4 of nox implementation - adding auto-format targets #478

Merged
merged 8 commits into from Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions django_spanner/base.py
Expand Up @@ -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 = {
Expand Down
8 changes: 1 addition & 7 deletions django_spanner/compiler.py
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions django_spanner/introspection.py
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -208,7 +208,7 @@ def get_constraints(self, cursor, table_name):
idx_col.ORDINAL_POSITION
""".format(
table=quoted_table_name
),
)
)
for (
index_name,
Expand Down
9 changes: 2 additions & 7 deletions django_spanner/operations.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions django_spanner/schema.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion django_spanner/utils.py
Expand Up @@ -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__
)
)
5 changes: 4 additions & 1 deletion docs/conf.py
Expand Up @@ -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),
}

Expand Down
48 changes: 47 additions & 1 deletion noxfile.py
Expand Up @@ -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")
Expand All @@ -24,7 +70,7 @@ def default(session):
"py.test",
"--quiet",
os.path.join("tests", "spanner_dbapi"),
*session.posargs,
*session.posargs
)


Expand Down
9 changes: 9 additions & 0 deletions setup.cfg
Expand Up @@ -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
Expand Down
57 changes: 27 additions & 30 deletions setup.py
Expand Up @@ -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 = {}


Expand All @@ -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",
)
2 changes: 1 addition & 1 deletion spanner_dbapi/connection.py
Expand Up @@ -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

Expand Down
8 changes: 3 additions & 5 deletions spanner_dbapi/cursor.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion spanner_dbapi/parse_utils.py
Expand Up @@ -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
Expand Down
22 changes: 16 additions & 6 deletions tests/spanner_dbapi/test_connect.py
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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)

Expand Down