diff --git a/.flake8 b/.flake8 index 29227d4c..2e438749 100644 --- a/.flake8 +++ b/.flake8 @@ -16,7 +16,7 @@ # Generated by synthtool. DO NOT EDIT! [flake8] -ignore = E203, E266, E501, W503 +ignore = E203, E231, E266, E501, W503 exclude = # Exclude generated code. **/proto/** diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 7e08e05a..757c9dca 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5d8da01438ece4021d135433f2cf3227aa39ef0eaccc941d62aa35e6902832ae + digest: sha256:81ed5ecdfc7cac5b699ba4537376f3563f6f04122c4ec9e735d3b3dc1d43dd32 +# created: 2022-05-05T22:08:23.383410683Z diff --git a/.github/auto-approve.yml b/.github/auto-approve.yml new file mode 100644 index 00000000..311ebbb8 --- /dev/null +++ b/.github/auto-approve.yml @@ -0,0 +1,3 @@ +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/auto-approve +processes: + - "OwlBotTemplateChanges" diff --git a/.github/auto-label.yaml b/.github/auto-label.yaml new file mode 100644 index 00000000..41bff0b5 --- /dev/null +++ b/.github/auto-label.yaml @@ -0,0 +1,15 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +requestsize: + enabled: true diff --git a/.kokoro/docker/docs/Dockerfile b/.kokoro/docker/docs/Dockerfile index 4e1b1fb8..238b87b9 100644 --- a/.kokoro/docker/docs/Dockerfile +++ b/.kokoro/docker/docs/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ubuntu:20.04 +from ubuntu:22.04 ENV DEBIAN_FRONTEND noninteractive @@ -60,8 +60,24 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb +###################### Install python 3.8.11 + +# Download python 3.8.11 +RUN wget https://www.python.org/ftp/python/3.8.11/Python-3.8.11.tgz + +# Extract files +RUN tar -xvf Python-3.8.11.tgz + +# Install python 3.8.11 +RUN ./Python-3.8.11/configure --enable-optimizations +RUN make altinstall + +###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3.8 /tmp/get-pip.py \ + && python3 /tmp/get-pip.py \ && rm /tmp/get-pip.py +# Test pip +RUN python3 -m pip + CMD ["python3.8"] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62eb5a77..46d23716 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 22.3.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ce484f..7ea96b9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,14 +14,21 @@ Older versions of this project were distributed as [pybigquery][0]. [2]: https://pypi.org/project/pybigquery/#history -### [1.4.3](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.2...v1.4.3) (2022-03-22) +## [1.4.4](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.3...v1.4.4) (2022-06-03) + + +### Documentation + +* fix changelog header to consistent size ([#461](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/461)) ([177e70a](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/177e70afb79420541021895fd0a082beb1704cf4)) + +## [1.4.3](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.2...v1.4.3) (2022-03-22) ### Bug Fixes * correct license text from Apache to MIT ([#436](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/436)) ([dbf7501](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/dbf7501c26157d3776f5a68254898758ee43a667)) -### [1.4.2](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.1...v1.4.2) (2022-03-22) +## [1.4.2](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.1...v1.4.2) (2022-03-22) ### Bug Fixes @@ -34,7 +41,7 @@ Older versions of this project were distributed as [pybigquery][0]. * require google-cloud-bigquery-storage to avoid performance warning ([#414](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/414)) ([ff3273f](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/ff3273feacfa1f34bb9090f28f11c2ac470759fc)) -### [1.4.1](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.0...v1.4.1) (2022-03-07) +## [1.4.1](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.4.0...v1.4.1) (2022-03-07) ### Bug Fixes @@ -60,14 +67,14 @@ Older versions of this project were distributed as [pybigquery][0]. * Enable support for 3.10 ([#381](https://www.github.com/googleapis/python-bigquery-sqlalchemy/issues/381)) ([4b3505b](https://www.github.com/googleapis/python-bigquery-sqlalchemy/commit/4b3505b3d3a4293ea127fc3c483e3e7de04fbd04)) -### [1.2.2](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.1...v1.2.2) (2021-10-29) +## [1.2.2](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.1...v1.2.2) (2021-10-29) ### Bug Fixes * avoid aliasing known tables used in CTEs ([#369](https://www.github.com/googleapis/python-bigquery-sqlalchemy/issues/369)) ([4b05d21](https://www.github.com/googleapis/python-bigquery-sqlalchemy/commit/4b05d21b8dc89339a69df87183f8893bf02459c5)) -### [1.2.1](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.0...v1.2.1) (2021-10-27) +## [1.2.1](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.0...v1.2.1) (2021-10-27) ### Bug Fixes diff --git a/dev_requirements.txt b/dev_requirements.txt index 933bd8fd..8ba88cdd 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,7 +1,6 @@ sqlalchemy>=1.1.9 google-cloud-bigquery>=1.6.0 future==0.18.2 - -pytest==6.2.5 -pytest-flake8==1.1.0 -pytz==2021.3 \ No newline at end of file +pytest===6.2.5 +pytest-flake8===1.1.0 # versions 1.1.1 and above require pytest 7 +pytz==2022.1 diff --git a/docs/conf.py b/docs/conf.py index 620d2a06..dbc0454b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -314,7 +314,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (root_doc, "sqlalchemy-bigquery", "sqlalchemy-bigquery Documentation", [author], 1,) + ( + root_doc, + "sqlalchemy-bigquery", + "sqlalchemy-bigquery Documentation", + [author], + 1, + ) ] # If true, show URL addresses after external links. @@ -355,7 +361,10 @@ intersphinx_mapping = { "python": ("https://python.readthedocs.org/en/latest/", None), "google-auth": ("https://googleapis.dev/python/google-auth/latest/", 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.github.io/grpc/python/", None), "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), diff --git a/noxfile.py b/noxfile.py index b50a212c..8a2b685e 100644 --- a/noxfile.py +++ b/noxfile.py @@ -21,18 +21,65 @@ import pathlib import re import shutil +import warnings import nox - -BLACK_VERSION = "black==19.10b0" -BLACK_PATHS = ["docs", "sqlalchemy_bigquery", "tests", "noxfile.py", "setup.py"] +BLACK_VERSION = "black==22.3.0" +ISORT_VERSION = "isort==5.10.1" +LINT_PATHS = ["docs", "sqlalchemy_bigquery", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.8" +UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] +UNIT_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "asyncmock", + "pytest", + "pytest-cov", + "pytest-asyncio", +] +UNIT_TEST_EXTERNAL_DEPENDENCIES = [] +UNIT_TEST_LOCAL_DEPENDENCIES = [] +UNIT_TEST_DEPENDENCIES = [] +UNIT_TEST_EXTRAS = [ + "tests", +] +UNIT_TEST_EXTRAS_BY_PYTHON = { + "3.8": [ + "tests", + "alembic", + ], + "3.10": [ + "tests", + "geography", + ], +} + + # We're using two Python versions to test with sqlalchemy 1.3 and 1.4. SYSTEM_TEST_PYTHON_VERSIONS = ["3.8", "3.10"] -UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] +SYSTEM_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "pytest", + "google-cloud-testutils", +] +SYSTEM_TEST_EXTERNAL_DEPENDENCIES = [] +SYSTEM_TEST_LOCAL_DEPENDENCIES = [] +SYSTEM_TEST_DEPENDENCIES = [] +SYSTEM_TEST_EXTRAS = [ + "tests", +] +SYSTEM_TEST_EXTRAS_BY_PYTHON = { + "3.8": [ + "tests", + "alembic", + ], + "3.10": [ + "tests", + "geography", + ], +} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() @@ -61,7 +108,9 @@ def lint(session): """ session.install("flake8", BLACK_VERSION) session.run( - "black", "--check", *BLACK_PATHS, + "black", + "--check", + *LINT_PATHS, ) session.run("flake8", "sqlalchemy_bigquery", "tests") @@ -71,7 +120,28 @@ def blacken(session): """Run black. Format code to uniform standard.""" session.install(BLACK_VERSION) session.run( - "black", *BLACK_PATHS, + "black", + *LINT_PATHS, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def format(session): + """ + Run isort to sort imports. Then run black + to format code to uniform standard. + """ + session.install(BLACK_VERSION, ISORT_VERSION) + # Use the --fss option to sort imports using strict alphabetical order. + # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections + session.run( + "isort", + "--fss", + *LINT_PATHS, + ) + session.run( + "black", + *LINT_PATHS, ) @@ -82,29 +152,41 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") +def install_unittest_dependencies(session, *constraints): + standard_deps = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_DEPENDENCIES + session.install(*standard_deps, *constraints) + + if UNIT_TEST_EXTERNAL_DEPENDENCIES: + warnings.warn( + "'unit_test_external_dependencies' is deprecated. Instead, please " + "use 'unit_test_dependencies' or 'unit_test_local_dependencies'.", + DeprecationWarning, + ) + session.install(*UNIT_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_LOCAL_DEPENDENCIES: + session.install(*UNIT_TEST_LOCAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_EXTRAS_BY_PYTHON: + extras = UNIT_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif UNIT_TEST_EXTRAS: + extras = UNIT_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + def default(session): # Install all test dependencies, then install this package in-place. constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.install( - "mock", - "asyncmock", - "pytest", - "pytest-cov", - "pytest-asyncio", - "-c", - constraints_path, - ) - - if session.python == "3.8": - extras = "[tests,alembic]" - elif session.python == "3.10": - extras = "[tests,geography]" - else: - extras = "[tests]" - session.install("-e", f".{extras}", "-c", constraints_path) + install_unittest_dependencies(session, "-c", constraints_path) # Run py.test against the unit tests. session.run( @@ -128,6 +210,35 @@ def unit(session): default(session) +def install_systemtest_dependencies(session, *constraints): + + # Use pre-release gRPC for system tests. + session.install("--pre", "grpcio") + + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTERNAL_DEPENDENCIES: + session.install(*SYSTEM_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_LOCAL_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_LOCAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTRAS_BY_PYTHON: + extras = SYSTEM_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif SYSTEM_TEST_EXTRAS: + extras = SYSTEM_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session): """Run the system test suite.""" @@ -150,19 +261,7 @@ def system(session): if not system_test_exists and not system_test_folder_exists: session.skip("System tests were not found") - # Use pre-release gRPC for system tests. - session.install("--pre", "grpcio") - - # Install all test dependencies, then install this package into the - # virtualenv's dist-packages. - session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path) - if session.python == "3.8": - extras = "[tests,alembic]" - elif session.python == "3.10": - extras = "[tests,geography]" - else: - extras = "[tests]" - session.install("-e", f".{extras}", "-c", constraints_path) + install_systemtest_dependencies(session, "-c", constraints_path) # Run py.test against the system tests. if system_test_exists: diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index 85f5836d..a40410b5 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -29,7 +29,8 @@ # WARNING - WARNING - WARNING - WARNING - WARNING # WARNING - WARNING - WARNING - WARNING - WARNING -BLACK_VERSION = "black==19.10b0" +BLACK_VERSION = "black==22.3.0" +ISORT_VERSION = "isort==5.10.1" # Copy `noxfile_config.py` to your directory and modify it instead. @@ -168,12 +169,33 @@ def lint(session: nox.sessions.Session) -> None: @nox.session def blacken(session: nox.sessions.Session) -> None: + """Run black. Format code to uniform standard.""" session.install(BLACK_VERSION) python_files = [path for path in os.listdir(".") if path.endswith(".py")] session.run("black", *python_files) +# +# format = isort + black +# + + +@nox.session +def format(session: nox.sessions.Session) -> None: + """ + Run isort to sort imports. Then run black + to format code to uniform standard. + """ + session.install(BLACK_VERSION, ISORT_VERSION) + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + # Use the --fss option to sort imports using strict alphabetical order. + # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections + session.run("isort", "--fss", *python_files) + session.run("black", *python_files) + + # # Sample Tests # @@ -253,7 +275,7 @@ def py(session: nox.sessions.Session) -> None: def _get_repo_root() -> Optional[str]: - """ Returns the root folder of the project. """ + """Returns the root folder of the project.""" # Get root of this repository. Assume we don't have directories nested deeper than 10 items. p = Path(os.getcwd()) for i in range(10): diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 4055c210..afd11b44 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,6 +1,6 @@ attrs==21.4.0 -click==8.0.4 -google-auth==2.6.0 +click==8.1.2 +google-auth==2.6.2 google-cloud-testutils==1.3.1 iniconfig==1.1.1 packaging==21.3 @@ -9,7 +9,7 @@ py==1.11.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.7 -pytest==6.2.5 +pytest===6.2.5 rsa==4.8 six==1.16.0 toml==0.10.2 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 4266837c..241c5e1e 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,34 +1,34 @@ -alembic==1.7.6 +alembic==1.7.7 certifi==2021.10.8 charset-normalizer==2.0.12 future==0.18.2 geoalchemy2==0.11.1 -google-api-core[grpc]==2.7.0 -google-auth==2.6.0 -google-cloud-bigquery==2.34.2 +google-api-core[grpc]==2.7.1 +google-auth==2.6.2 +google-cloud-bigquery==3.0.1 google-cloud-core==2.2.3 google-crc32c==1.3.0 google-resumable-media==2.3.2 -googleapis-common-protos==1.55.0 +googleapis-common-protos==1.56.0 greenlet==1.1.2 -grpcio==1.44.0 -grpcio-status==1.44.0 +grpcio==1.45.0 +grpcio-status==1.45.0 idna==3.3 -importlib-resources==5.4.0 -mako==1.1.6 -markupsafe==2.1.0 +importlib-resources==5.6.0 +mako==1.2.0 +markupsafe==2.1.1 packaging==21.3 proto-plus==1.20.3 -protobuf==3.19.4 +protobuf==3.20.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.7 python-dateutil==2.8.2 -pytz==2021.3 +pytz==2022.1 requests==2.27.1 rsa==4.8 shapely==1.8.1.post1 six==1.16.0 -sqlalchemy==1.4.27 +sqlalchemy===1.4.27 typing-extensions==4.1.1 -urllib3==1.26.8 +urllib3==1.26.9 diff --git a/scripts/readme-gen/readme_gen.py b/scripts/readme-gen/readme_gen.py index d309d6e9..91b59676 100644 --- a/scripts/readme-gen/readme_gen.py +++ b/scripts/readme-gen/readme_gen.py @@ -28,7 +28,10 @@ jinja_env = jinja2.Environment( trim_blocks=True, loader=jinja2.FileSystemLoader( - os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates')))) + os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")) + ), + autoescape=True, +) README_TMPL = jinja_env.get_template('README.tmpl.rst') diff --git a/sqlalchemy_bigquery/_struct.py b/sqlalchemy_bigquery/_struct.py index a3d9aba4..6ebb5a64 100644 --- a/sqlalchemy_bigquery/_struct.py +++ b/sqlalchemy_bigquery/_struct.py @@ -124,12 +124,14 @@ def _field_index(self, name, operator): bindparam_type=sqlalchemy.types.String(), ) - else: def _field_index(self, name, operator): return sqlalchemy.sql.default_comparator._check_literal( - self.expr, operator, name, bindparam_type=sqlalchemy.types.String(), + self.expr, + operator, + name, + bindparam_type=sqlalchemy.types.String(), ) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index ca1772a0..48455836 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -76,7 +76,8 @@ class BigQueryIdentifierPreparer(IdentifierPreparer): def __init__(self, dialect): super(BigQueryIdentifierPreparer, self).__init__( - dialect, initial_quote="`", + dialect, + initial_quote="`", ) def quote_column(self, value): @@ -349,7 +350,7 @@ def group_by_clause(self, select, **kw): __expanding_conflict = "" if __sqlalchemy_version_info < (1, 4, 27) else "__" __in_expanding_bind = _helpers.substitute_string_re_method( - fr""" + rf""" \sIN\s\( # ' IN (' ( {__expanding_conflict}\[ # Expanding placeholder @@ -435,7 +436,7 @@ def visit_notendswith_op_binary(self, binary, operator, **kw): __placeholder = re.compile(r"%\(([^\]:]+)(:[^\]:]+)?\)s$").match __expanded_param = re.compile( - fr"\({__expanding_conflict}\[" fr"{__expanding_text}" fr"_[^\]]+\]\)$" + rf"\({__expanding_conflict}\[" rf"{__expanding_text}" rf"_[^\]]+\]\)$" ).match __remove_type_parameter = _helpers.substitute_string_re_method( @@ -681,8 +682,7 @@ def literal_processor(self, dialect): class BQClassTaggedStr(sqlalchemy.sql.type_api.TypeEngine): - """Type that can get literals via str - """ + """Type that can get literals via str""" @staticmethod def process_literal_as_class_tagged_str(value): @@ -693,8 +693,7 @@ def literal_processor(self, dialect): class BQTimestamp(sqlalchemy.sql.type_api.TypeEngine): - """Type that can get literals via str - """ + """Type that can get literals via str""" @staticmethod def process_timestamp_literal(value): diff --git a/sqlalchemy_bigquery/geography.py b/sqlalchemy_bigquery/geography.py index 16384dd4..ec96a715 100644 --- a/sqlalchemy_bigquery/geography.py +++ b/sqlalchemy_bigquery/geography.py @@ -100,7 +100,9 @@ class Lake(Base): def __init__(self): super().__init__( - geometry_type=None, spatial_index=False, srid=SRID, + geometry_type=None, + spatial_index=False, + srid=SRID, ) self.extended = True @@ -198,32 +200,74 @@ def _fixup_st_arguments(element, compiler, **kw): st_boundary=(GEOGRAPHY,), st_centroid=(GEOGRAPHY,), st_centroid_agg=(GEOGRAPHY,), - st_closestpoint=(GEOGRAPHY, GEOGRAPHY,), + st_closestpoint=( + GEOGRAPHY, + GEOGRAPHY, + ), st_clusterdbscan=(GEOGRAPHY,), - st_contains=(GEOGRAPHY, GEOGRAPHY,), + st_contains=( + GEOGRAPHY, + GEOGRAPHY, + ), st_convexhull=(GEOGRAPHY,), - st_coveredby=(GEOGRAPHY, GEOGRAPHY,), - st_covers=(GEOGRAPHY, GEOGRAPHY,), - st_difference=(GEOGRAPHY, GEOGRAPHY,), + st_coveredby=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_covers=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_difference=( + GEOGRAPHY, + GEOGRAPHY, + ), st_dimension=(GEOGRAPHY,), - st_disjoint=(GEOGRAPHY, GEOGRAPHY,), - st_distance=(GEOGRAPHY, GEOGRAPHY,), + st_disjoint=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_distance=( + GEOGRAPHY, + GEOGRAPHY, + ), st_dump=(GEOGRAPHY,), - st_dwithin=(GEOGRAPHY, GEOGRAPHY,), + st_dwithin=( + GEOGRAPHY, + GEOGRAPHY, + ), st_endpoint=(GEOGRAPHY,), - st_equals=(GEOGRAPHY, GEOGRAPHY,), + st_equals=( + GEOGRAPHY, + GEOGRAPHY, + ), st_exteriorring=(GEOGRAPHY,), st_geohash=(GEOGRAPHY,), - st_intersection=(GEOGRAPHY, GEOGRAPHY,), - st_intersects=(GEOGRAPHY, GEOGRAPHY,), + st_intersection=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_intersects=( + GEOGRAPHY, + GEOGRAPHY, + ), st_intersectsbox=(GEOGRAPHY,), st_iscollection=(GEOGRAPHY,), st_isempty=(GEOGRAPHY,), st_length=(GEOGRAPHY,), - st_makeline=(GEOGRAPHY, GEOGRAPHY,), - st_makepolygon=(GEOGRAPHY, GEOGRAPHY,), + st_makeline=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_makepolygon=( + GEOGRAPHY, + GEOGRAPHY, + ), st_makepolygonoriented=(GEOGRAPHY,), - st_maxdistance=(GEOGRAPHY, GEOGRAPHY,), + st_maxdistance=( + GEOGRAPHY, + GEOGRAPHY, + ), st_npoints=(GEOGRAPHY,), st_numpoints=(GEOGRAPHY,), st_perimeter=(GEOGRAPHY,), @@ -231,10 +275,19 @@ def _fixup_st_arguments(element, compiler, **kw): st_simplify=(GEOGRAPHY,), st_snaptogrid=(GEOGRAPHY,), st_startpoint=(GEOGRAPHY,), - st_touches=(GEOGRAPHY, GEOGRAPHY,), - st_union=(GEOGRAPHY, GEOGRAPHY,), + st_touches=( + GEOGRAPHY, + GEOGRAPHY, + ), + st_union=( + GEOGRAPHY, + GEOGRAPHY, + ), st_union_agg=(GEOGRAPHY,), - st_within=(GEOGRAPHY, GEOGRAPHY,), + st_within=( + GEOGRAPHY, + GEOGRAPHY, + ), st_x=(GEOGRAPHY,), st_y=(GEOGRAPHY,), ) diff --git a/sqlalchemy_bigquery/version.py b/sqlalchemy_bigquery/version.py index 70dfa1a1..dab676d1 100644 --- a/sqlalchemy_bigquery/version.py +++ b/sqlalchemy_bigquery/version.py @@ -17,4 +17,4 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__version__ = "1.4.3" +__version__ = "1.4.4" diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt index e69de29b..9d2df4fe 100644 --- a/testing/constraints-3.7.txt +++ b/testing/constraints-3.7.txt @@ -0,0 +1,12 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List *all* library dependencies and extras in this file. +# Pin the version to the lower bound. +# +# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", +sqlalchemy==1.2.0 +google-auth==1.25.0 +google-cloud-bigquery==2.25.2 +google-cloud-bigquery-storage==2.0.0 +google-api-core==1.31.5 +pyarrow==3.0.0 diff --git a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py index 156e6167..5d575fc7 100644 --- a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py +++ b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py @@ -69,7 +69,6 @@ def literal(value): with mock.patch("sqlalchemy.testing.suite.test_types.literal", literal): super(TimestampMicrosecondsTest, self).test_literal() - else: from sqlalchemy.testing.suite import ( FetchLimitOffsetTest as _FetchLimitOffsetTest, @@ -95,7 +94,9 @@ def test_limit_render_multiple_times(self, connection): u = sqlalchemy.union(select(stmt), select(stmt)).subquery().select() self._assert_result( - connection, u, [(1,)], + connection, + u, + [(1,)], ) del DifficultParametersTest # exercises column names illegal in BQ @@ -193,7 +194,10 @@ def test_select_exists(self, connection): eq_( connection.execute( select([stuff.c.id]).where( - and_(stuff.c.id == 1, exists().where(stuff.c.data == "some data"),) + and_( + stuff.c.id == 1, + exists().where(stuff.c.data == "some data"), + ) ) ).fetchall(), [(1,)], diff --git a/tests/system/conftest.py b/tests/system/conftest.py index 04803ebd..23d244f2 100644 --- a/tests/system/conftest.py +++ b/tests/system/conftest.py @@ -53,7 +53,9 @@ def load_sample_data( with open(DATA_DIR / filename, "rb") as data_file: return bigquery_client.load_table_from_file( - data_file, full_table_id, job_config=sample_config, + data_file, + full_table_id, + job_config=sample_config, ) @@ -86,7 +88,9 @@ def bigquery_dataset( filename="sample_one_row.json", ) job2.result() - view = bigquery.Table(f"{project_id}.{dataset_id}.sample_view",) + view = bigquery.Table( + f"{project_id}.{dataset_id}.sample_view", + ) view.view_query = f"SELECT string FROM `{dataset_id}.sample`" bigquery_client.create_table(view) yield dataset_id diff --git a/tests/system/test__struct.py b/tests/system/test__struct.py index 24863056..bb7958c9 100644 --- a/tests/system/test__struct.py +++ b/tests/system/test__struct.py @@ -148,7 +148,10 @@ def test_unnest_and_struct_access_233(engine, bigquery_dataset, metadata): metadata.create_all(engine) conn.execute( - mock_table.insert(), dict(mock_id="x"), dict(mock_id="y"), dict(mock_id="z"), + mock_table.insert(), + dict(mock_id="x"), + dict(mock_id="y"), + dict(mock_id="z"), ) conn.execute( another_mock_table.insert(), @@ -160,7 +163,8 @@ def test_unnest_and_struct_access_233(engine, bigquery_dataset, metadata): ).subquery() join = mock_table.join( - subquery, subquery.c.another_mock_objects["object_id"] == mock_table.c.mock_id, + subquery, + subquery.c.another_mock_objects["object_id"] == mock_table.c.mock_id, ) query = select(mock_table).select_from(join) diff --git a/tests/system/test_geography.py b/tests/system/test_geography.py index 18bcc7d4..7189eebb 100644 --- a/tests/system/test_geography.py +++ b/tests/system/test_geography.py @@ -56,7 +56,8 @@ def test_geoalchemy2_core(bigquery_dataset): conn.execute( lake_table.insert().values( - name="Majeur", geog="POLYGON((0 0,1 0,1 1,0 1,0 0))", + name="Majeur", + geog="POLYGON((0 0,1 0,1 1,0 1,0 0))", ) ) @@ -113,7 +114,8 @@ def test_geoalchemy2_core(bigquery_dataset): conn.execute( lake_table.insert().values( - name="test2", geog=WKT("POLYGON((1 0,3 0,3 2,1 2,1 0))"), + name="test2", + geog=WKT("POLYGON((1 0,3 0,3 2,1 2,1 0))"), ) ) assert ( diff --git a/tests/system/test_helpers.py b/tests/system/test_helpers.py index 62f22688..42cfab7f 100644 --- a/tests/system/test_helpers.py +++ b/tests/system/test_helpers.py @@ -54,7 +54,8 @@ def test_create_bigquery_client_with_credentials_path_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ bqclient = module_under_test.create_bigquery_client( - credentials_path=credentials_path, project_id="connection-url-project", + credentials_path=credentials_path, + project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" @@ -76,7 +77,8 @@ def test_create_bigquery_client_with_credentials_info_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ bqclient = module_under_test.create_bigquery_client( - credentials_info=credentials_info, project_id="connection-url-project", + credentials_info=credentials_info, + project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" @@ -98,6 +100,7 @@ def test_create_bigquery_client_with_credentials_base64_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ bqclient = module_under_test.create_bigquery_client( - credentials_base64=credentials_base64, project_id="connection-url-project", + credentials_base64=credentials_base64, + project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" diff --git a/tests/system/test_sqlalchemy_bigquery.py b/tests/system/test_sqlalchemy_bigquery.py index 564c5e68..0772e10a 100644 --- a/tests/system/test_sqlalchemy_bigquery.py +++ b/tests/system/test_sqlalchemy_bigquery.py @@ -305,7 +305,8 @@ def test_content_from_reflect(engine, table_one_row): def test_unicode(engine, table_one_row): unicode_str = "白人看不懂" returned_str = sqlalchemy.select( - [expression.bindparam("好", unicode_str)], from_obj=table_one_row, + [expression.bindparam("好", unicode_str)], + from_obj=table_one_row, ).scalar() assert returned_str == unicode_str @@ -756,7 +757,9 @@ def test_unnest(engine, bigquery_dataset): conn = engine.connect() metadata = MetaData() table = Table( - f"{bigquery_dataset}.test_unnest", metadata, Column("objects", ARRAY(String)), + f"{bigquery_dataset}.test_unnest", + metadata, + Column("objects", ARRAY(String)), ) metadata.create_all(engine) conn.execute( diff --git a/tests/unit/fauxdbi.py b/tests/unit/fauxdbi.py index 631996af..c6ee2a73 100644 --- a/tests/unit/fauxdbi.py +++ b/tests/unit/fauxdbi.py @@ -172,7 +172,8 @@ def __normalize_array_types(self, m): return m.group(0).replace("<", "_").replace(">", "_") def __handle_array_types( - self, operation, + self, + operation, ): if self.__create_table(operation): return self.__normalize_array_types(operation) @@ -187,7 +188,8 @@ def __parse_dateish(type_, value): if type_ == "datetime": return datetime.datetime.strptime( - value, "%Y-%m-%d %H:%M:%S.%f" if "." in value else "%Y-%m-%d %H:%M:%S", + value, + "%Y-%m-%d %H:%M:%S.%f" if "." in value else "%Y-%m-%d %H:%M:%S", ) elif type_ == "date": return datetime.date(*map(int, value.split("-"))) diff --git a/tests/unit/test__struct.py b/tests/unit/test__struct.py index 26ccec54..77577066 100644 --- a/tests/unit/test__struct.py +++ b/tests/unit/test__struct.py @@ -52,7 +52,9 @@ def test_bind_processor(): def _col(): return sqlalchemy.Table( - "t", sqlalchemy.MetaData(), sqlalchemy.Column("person", _test_struct()), + "t", + sqlalchemy.MetaData(), + sqlalchemy.Column("person", _test_struct()), ).c.person diff --git a/tests/unit/test_catalog_functions.py b/tests/unit/test_catalog_functions.py index fd7d0d63..78614c9f 100644 --- a/tests/unit/test_catalog_functions.py +++ b/tests/unit/test_catalog_functions.py @@ -127,9 +127,15 @@ def test_get_indexes(faux_conn): client.tables.foo.clustering_fields = ["user_email", "store_code"] assert faux_conn.dialect.get_indexes(faux_conn, "foo") == [ - dict(name="partition", column_names=["tm"], unique=False,), dict( - name="clustering", column_names=["user_email", "store_code"], unique=False, + name="partition", + column_names=["tm"], + unique=False, + ), + dict( + name="clustering", + column_names=["user_email", "store_code"], + unique=False, ), ] diff --git a/tests/unit/test_comments.py b/tests/unit/test_comments.py index df7f07a3..6feba866 100644 --- a/tests/unit/test_comments.py +++ b/tests/unit/test_comments.py @@ -39,7 +39,9 @@ def test_inline_comments(faux_conn): def test_set_drop_table_comment(faux_conn): table = setup_table( - faux_conn, "some_table", sqlalchemy.Column("id", sqlalchemy.Integer), + faux_conn, + "some_table", + sqlalchemy.Column("id", sqlalchemy.Integer), ) dialect = faux_conn.dialect diff --git a/tests/unit/test_compiler.py b/tests/unit/test_compiler.py index 84a93508..db02e593 100644 --- a/tests/unit/test_compiler.py +++ b/tests/unit/test_compiler.py @@ -26,7 +26,9 @@ def test_constraints_are_ignored(faux_conn, metadata): sqlalchemy.Table( - "ref", metadata, sqlalchemy.Column("id", sqlalchemy.Integer), + "ref", + metadata, + sqlalchemy.Column("id", sqlalchemy.Integer), ) sqlalchemy.Table( "some_table", diff --git a/tests/unit/test_geography.py b/tests/unit/test_geography.py index fafc0a80..25d3c605 100644 --- a/tests/unit/test_geography.py +++ b/tests/unit/test_geography.py @@ -25,8 +25,7 @@ def test_geoalchemy2_core(faux_conn, last_query): - """Make sure GeoAlchemy 2 Core Tutorial works as adapted to only having geometry - """ + """Make sure GeoAlchemy 2 Core Tutorial works as adapted to only having geometry""" conn = faux_conn # Create the Table @@ -42,7 +41,8 @@ def test_geoalchemy2_core(faux_conn, last_query): conn.execute( lake_table.insert().values( - name="Majeur", geog="POLYGON((0 0,1 0,1 1,0 1,0 0))", + name="Majeur", + geog="POLYGON((0 0,1 0,1 1,0 1,0 0))", ) ) @@ -132,7 +132,8 @@ def test_geoalchemy2_core(faux_conn, last_query): conn.execute( lake_table.insert().values( - name="test2", geog=WKT("POLYGON((1 0,3 0,3 2,1 2,1 0))"), + name="test2", + geog=WKT("POLYGON((1 0,3 0,3 2,1 2,1 0))"), ) ) last_query( diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 9400f1ed..02bc8bee 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -34,8 +34,8 @@ def module_under_test(): def test_create_bigquery_client_with_credentials_path(monkeypatch, module_under_test): mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_file.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_file.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) @@ -54,13 +54,14 @@ def test_create_bigquery_client_with_credentials_path_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_file.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_file.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) bqclient = module_under_test.create_bigquery_client( - credentials_path="path/to/key.json", project_id="connection-url-project", + credentials_path="path/to/key.json", + project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" @@ -68,8 +69,8 @@ def test_create_bigquery_client_with_credentials_path_respects_project( def test_create_bigquery_client_with_credentials_info(monkeypatch, module_under_test): mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_info.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_info.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) @@ -91,8 +92,8 @@ def test_create_bigquery_client_with_credentials_info_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_info.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_info.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) @@ -109,8 +110,8 @@ def test_create_bigquery_client_with_credentials_info_respects_project( def test_create_bigquery_client_with_credentials_base64(monkeypatch, module_under_test): mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_info.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_info.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) @@ -135,8 +136,8 @@ def test_create_bigquery_client_with_credentials_base64_respects_project( https://github.com/googleapis/python-bigquery-sqlalchemy/issues/48 """ mock_service_account = mock.create_autospec(service_account.Credentials) - mock_service_account.from_service_account_info.return_value = AnonymousCredentialsWithProject( - "service-account-project" + mock_service_account.from_service_account_info.return_value = ( + AnonymousCredentialsWithProject("service-account-project") ) monkeypatch.setattr(service_account, "Credentials", mock_service_account) @@ -147,7 +148,8 @@ def test_create_bigquery_client_with_credentials_base64_respects_project( credentials_base64 = base64.b64encode(json.dumps(credentials_info).encode()) bqclient = module_under_test.create_bigquery_client( - credentials_base64=credentials_base64, project_id="connection-url-project", + credentials_base64=credentials_base64, + project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" diff --git a/tests/unit/test_select.py b/tests/unit/test_select.py index 33c657f7..08fc0225 100644 --- a/tests/unit/test_select.py +++ b/tests/unit/test_select.py @@ -174,7 +174,9 @@ def test_select_struct(faux_conn, metadata): from sqlalchemy_bigquery import STRUCT table = sqlalchemy.Table( - "t", metadata, sqlalchemy.Column("x", STRUCT(y=sqlalchemy.Integer)), + "t", + metadata, + sqlalchemy.Column("x", STRUCT(y=sqlalchemy.Integer)), ) faux_conn.ex("create table t (x RECORD)") @@ -198,7 +200,9 @@ def test_force_quote(faux_conn): from sqlalchemy.sql.elements import quoted_name table = setup_table( - faux_conn, "t", sqlalchemy.Column(quoted_name("foo", True), sqlalchemy.Integer), + faux_conn, + "t", + sqlalchemy.Column(quoted_name("foo", True), sqlalchemy.Integer), ) faux_conn.execute(sqlalchemy.select([table])) assert faux_conn.test_data["execute"][-1][0] == ("SELECT `t`.`foo` \nFROM `t`") @@ -435,7 +439,9 @@ def test_unnest_w_no_table_references(faux_conn, alias): def test_array_indexing(faux_conn, metadata): t = sqlalchemy.Table( - "t", metadata, sqlalchemy.Column("a", sqlalchemy.ARRAY(sqlalchemy.String)), + "t", + metadata, + sqlalchemy.Column("a", sqlalchemy.ARRAY(sqlalchemy.String)), ) got = str(sqlalchemy.select([t.c.a[0]]).compile(faux_conn.engine)) assert got == "SELECT `t`.`a`[OFFSET(%(a_1:INT64)s)] AS `anon_1` \nFROM `t`"