Skip to content

feat!: Support SQLAlchemy 2.0, backwards compatibility for 1.4.16+ #920

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

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ffc491f
feat!: Support SQLAlchemy 2.0, drop support for 1.x
sharoonthomas Oct 26, 2023
c7f2751
Merge branch 'main' into sqla2
nayaknishant Nov 9, 2023
c74a0b9
constraints updated
nayaknishant Nov 2, 2023
001759e
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Nov 2, 2023
bc5725f
fixing README.rst
nayaknishant Nov 7, 2023
8d5c2cc
fixing README.rst
nayaknishant Nov 7, 2023
4a355c4
upping sqlalchemy version in constraints-3.8.txt
nayaknishant Nov 9, 2023
0079d11
adding 2.0 version restrictions to owlbot.py
nayaknishant Nov 9, 2023
89ea48e
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Nov 9, 2023
39b0d66
fix for
nayaknishant Nov 15, 2023
1e81318
Updated some compliance tests for sqla2 and bq
kiraksi Dec 1, 2023
eeffbe6
Addressed snippet errors
kiraksi Dec 2, 2023
02921c2
revert bad commit
kiraksi Dec 2, 2023
1e9a72e
More compliance tests checking
kiraksi Dec 5, 2023
a84af4b
reformatted with black
kiraksi Dec 5, 2023
c09a676
Changed more compliance tests, updated requirements for testing
kiraksi Dec 8, 2023
6999f62
Merge remote-tracking branch 'origin' into sqla2
chalmerlowe Dec 8, 2023
00274e0
Fixed attributeerror in failing sample test
kiraksi Dec 8, 2023
cb34e6b
Fixed geolography test failing issue
kiraksi Dec 11, 2023
08d93b0
Minor tweaks to tests and code
chalmerlowe Dec 11, 2023
a377a31
Merge branch 'main' of github.com:googleapis/python-bigquery-sqlalche…
chalmerlowe Dec 11, 2023
2492a0c
Merge branch 'sqla2' into sqla2
chalmerlowe Dec 11, 2023
4a5c291
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Dec 11, 2023
c4c9976
Fixed small error in compliance tests, added pip freeze for owl bot t…
kiraksi Dec 11, 2023
60735ff
merged from branch
kiraksi Dec 11, 2023
2612a70
Fixed some failing compliance tests by reformatting
kiraksi Dec 12, 2023
5d3d850
Added UuidTest to compliance tests
kiraksi Dec 13, 2023
a708987
merged main branch
kiraksi Dec 14, 2023
a3a4ee6
Moved back sqlalchemy constraints to 1.4
kiraksi Dec 15, 2023
a71e1ee
Update testing/constraints-3.8.txt
tswast Dec 15, 2023
708ec59
Fixed minimum version of sqlalchemy for 1.4 backwards compatibility
kiraksi Dec 18, 2023
9a9bc6b
Bumping support for sqlalchemy 1.4.16 for sample tests
kiraksi Dec 18, 2023
740bfcd
Bump setup.py sqlalchemy to 1.4.16
kiraksi Dec 18, 2023
75038b0
Updated compliance sqlalchemy to 1.4.16
kiraksi Dec 18, 2023
3c50f7d
Merge branch 'main' into sqla2
chalmerlowe Dec 19, 2023
455dbeb
Fixed broken code in last merged main, as we need to avoid duplicate …
kiraksi Dec 19, 2023
da9f228
modified tests for join order variation in 1.4 vs 2.0
kiraksi Dec 20, 2023
59f0a4e
typo
kiraksi Dec 20, 2023
9e8d4e7
Modified one compliance StringTest thats been flaky
kiraksi Dec 20, 2023
c0016ee
Merge branch 'main' into sqla2
kiraksi Jan 10, 2024
5503877
Updated docs
kiraksi Jan 10, 2024
b4010e1
minor fixes to noxfile and README
kiraksi Jan 12, 2024
07a1e65
Merge branch 'main' into sqla2
chalmerlowe Jan 12, 2024
1e54e77
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jan 16, 2024
e95aee1
cleaned up code from review, removed unnecessary code and files
kiraksi Jan 17, 2024
b8f8f21
Update tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py
chalmerlowe Jan 17, 2024
03fb7f7
Merge branch 'main' into sqla2
kiraksi Jan 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Maksym Voitko
Maxim Zudilov (mxmzdlv)
Maxime Beauchemin (mistercrunch)
Romain Rigaux
Sharoon Thomas (sharoonthomas)
Sumedh Sakdeo
Tim Swast (tswast)
Vince Broz
Expand Down
8 changes: 5 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ In order to use this library, you first need to go through the following steps:
.. _Setup Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html

.. note::
This library is only compatible with SQLAlchemy versions < 2.0.0
This library is a prerelease to gauge compatiblity with SQLAlchemy
versions >= 1.4.16 and < 2.1

Installation
------------
Expand Down Expand Up @@ -108,7 +109,8 @@ SQLAlchemy
from sqlalchemy.schema import *
engine = create_engine('bigquery://project')
table = Table('dataset.table', MetaData(bind=engine), autoload=True)
print(select([func.count('*')], from_obj=table).scalar())
print(select([func.count('*')], from_obj=table().scalar())


Project
^^^^^^^
Expand Down Expand Up @@ -281,7 +283,7 @@ If you need additional control, you can supply a BigQuery client of your own:

engine = create_engine(
'bigquery://some-project/some-dataset?user_supplied_client=True',
connect_args={'client': custom_bq_client},
connect_args={'client': custom_bq_client},
)


Expand Down
4 changes: 2 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def compliance(session):
session.skip("Compliance tests were not found")

session.install("--pre", "grpcio")
session.install("--pre", "--no-deps", "--upgrade", "sqlalchemy<2.0.0")
session.install("--pre", "--no-deps", "--upgrade", "sqlalchemy>=1.4.16,<2.1")
session.install(
"mock",
"pytest",
Expand Down Expand Up @@ -543,7 +543,7 @@ def prerelease_deps(session):

prerel_deps = [
"protobuf",
"sqlalchemy<2.0.0",
"sqlalchemy>=1.4.16,<2.1",
# dependency of grpc
"six",
"googleapis-common-protos",
Expand Down
72 changes: 37 additions & 35 deletions owlbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@
system_test_extras=extras,
system_test_extras_by_python=extras_by_python,
)
s.move(templated_files, excludes=[
# sqlalchemy-bigquery was originally licensed MIT
"LICENSE",
"docs/multiprocessing.rst",
# exclude gh actions as credentials are needed for tests
".github/workflows",
"README.rst",
])
s.move(
templated_files,
excludes=[
# sqlalchemy-bigquery was originally licensed MIT
"LICENSE",
"docs/multiprocessing.rst",
# exclude gh actions as credentials are needed for tests
".github/workflows",
"README.rst",
],
)

# ----------------------------------------------------------------------------
# Fixup files
Expand All @@ -59,7 +62,7 @@
[".coveragerc"],
"google/cloud/__init__.py",
"sqlalchemy_bigquery/requirements.py",
)
)

s.replace(
["noxfile.py"],
Expand All @@ -75,48 +78,49 @@


s.replace(
["noxfile.py"], "--cov=google", "--cov=sqlalchemy_bigquery",
["noxfile.py"],
"--cov=google",
"--cov=sqlalchemy_bigquery",
)


s.replace(
["noxfile.py"],
["noxfile.py"],
"\+ SYSTEM_TEST_EXTRAS",
"",
)


s.replace(
["noxfile.py"],
'''"protobuf",
# dependency of grpc''',
'''"protobuf",
"sqlalchemy<2.0.0",
# dependency of grpc''',
""""protobuf",
# dependency of grpc""",
""""protobuf",
"sqlalchemy>=1.4.16,<2.1",
# dependency of grpc""",
)


s.replace(
["noxfile.py"],
r"def default\(session\)",
"def default(session, install_extras=True)",
"def default(session, install_extras=True)",
)




def place_before(path, text, *before_text, escape=None):
replacement = "\n".join(before_text) + "\n" + text
if escape:
for c in escape:
text = text.replace(c, '\\' + c)
text = text.replace(c, "\\" + c)
s.replace([path], text, replacement)


place_before(
"noxfile.py",
"SYSTEM_TEST_PYTHON_VERSIONS=",
"",
"# We're using two Python versions to test with sqlalchemy 1.3 and 1.4.",
"# We're using two Python versions to test with sqlalchemy>=1.4.16",
)

place_before(
Expand All @@ -126,15 +130,15 @@ def place_before(path, text, *before_text, escape=None):
)


install_logic = '''
install_logic = """
if install_extras and session.python in ["3.11", "3.12"]:
install_target = ".[geography,alembic,tests,bqstorage]"
elif install_extras:
install_target = ".[all]"
else:
install_target = "."
session.install("-e", install_target, "-c", constraints_path)
'''
"""

place_before(
"noxfile.py",
Expand Down Expand Up @@ -163,7 +167,7 @@ def compliance(session):
session.skip("Compliance tests were not found")

session.install("--pre", "grpcio")
session.install("--pre", "--no-deps", "--upgrade", "sqlalchemy<2.0.0")
session.install("--pre", "--no-deps", "--upgrade", "sqlalchemy>=1.4.16,<2.1")
session.install(
"mock",
"pytest",
Expand Down Expand Up @@ -206,12 +210,11 @@ def compliance(session):
'''

place_before(
"noxfile.py",
"@nox.session(python=DEFAULT_PYTHON_VERSION)\n"
"def cover(session):",
compliance,
escape="()",
)
"noxfile.py",
"@nox.session(python=DEFAULT_PYTHON_VERSION)\n" "def cover(session):",
compliance,
escape="()",
)

s.replace(["noxfile.py"], '"alabaster"', '"alabaster", "geoalchemy2", "shapely"')

Expand Down Expand Up @@ -267,11 +270,10 @@ def system_noextras(session):

place_before(
"noxfile.py",
"@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1])\n"
"def compliance(session):",
"@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1])\n" "def compliance(session):",
system_noextras,
escape="()[]",
)
)


# Add DB config for SQLAlchemy dialect test suite.
Expand All @@ -288,7 +290,7 @@ def system_noextras(session):
[tool:pytest]
addopts= --tb native -v -r fxX -p no:warnings
python_files=tests/*test_*.py
"""
""",
)

# ----------------------------------------------------------------------------
Expand All @@ -299,7 +301,7 @@ def system_noextras(session):
python.py_samples(skip_readmes=True)

s.replace(
["./samples/snippets/noxfile.py"],
["./samples/snippets/noxfile.py"],
"""session.install\("-e", _get_repo_root\(\)\)""",
"""session.install("-e", _get_repo_root())
else:
Expand Down
2 changes: 1 addition & 1 deletion samples/snippets/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ requests==2.31.0
rsa==4.9
shapely==2.0.2
six==1.16.0
sqlalchemy===1.4.27
sqlalchemy==1.4.16
typing-extensions==4.9.0
urllib3==2.1.0
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ def readme():
# Until this issue is closed
# https://github.com/googleapis/google-cloud-python/issues/10566
"google-auth>=1.25.0,<3.0.0dev", # Work around pip wack.
"google-cloud-bigquery>=2.25.2,<4.0.0dev",
"google-cloud-bigquery>=3.3.6,<4.0.0dev",
"packaging",
"sqlalchemy>=1.2.0,<2.0.0dev",
"sqlalchemy>=1.4.16,<2.1",
],
extras_require=extras,
python_requires=">=3.8, <3.13",
Expand Down
2 changes: 2 additions & 0 deletions sqlalchemy_bigquery/_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ def _setup_getitem(self, name):
def __getattr__(self, name):
if name.lower() in self.expr.type._STRUCT_byname:
return self[name]
else:
raise AttributeError(name)

comparator_factory = Comparator

Expand Down
46 changes: 37 additions & 9 deletions sqlalchemy_bigquery/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def visit_table_valued_alias(self, element, **kw):
# For example, given SQLAlchemy code:
#
# print(
# select([func.unnest(foo.c.objects).alias('foo_objects').column])
# select(func.unnest(foo.c.objects).alias('foo_objects').column)
# .compile(engine))
#
# Left to it's own devices, SQLAlchemy would outout:
Expand Down Expand Up @@ -388,7 +388,7 @@ def visit_in_op_binary(self, binary, operator_, **kw):
self._generate_generic_binary(binary, " IN ", **kw)
)

def visit_empty_set_expr(self, element_types):
def visit_empty_set_expr(self, element_types, **kw):
return ""

def visit_not_in_op_binary(self, binary, operator, **kw):
Expand Down Expand Up @@ -644,15 +644,15 @@ class BigQueryDDLCompiler(DDLCompiler):
}

# BigQuery has no support for foreign keys.
def visit_foreign_key_constraint(self, constraint):
def visit_foreign_key_constraint(self, constraint, **kw):
return None

# BigQuery has no support for primary keys.
def visit_primary_key_constraint(self, constraint):
def visit_primary_key_constraint(self, constraint, **kw):
return None

# BigQuery has no support for unique constraints.
def visit_unique_constraint(self, constraint):
def visit_unique_constraint(self, constraint, **kw):
return None

def get_column_specification(self, column, **kwargs):
Expand Down Expand Up @@ -760,14 +760,14 @@ def post_create_table(self, table):

return " " + "\n".join(clauses)

def visit_set_table_comment(self, create):
def visit_set_table_comment(self, create, **kw):
table_name = self.preparer.format_table(create.element)
description = self.sql_compiler.render_literal_value(
create.element.comment, sqlalchemy.sql.sqltypes.String()
)
return f"ALTER TABLE {table_name} SET OPTIONS(description={description})"

def visit_drop_table_comment(self, drop):
def visit_drop_table_comment(self, drop, **kw):
table_name = self.preparer.format_table(drop.element)
return f"ALTER TABLE {table_name} SET OPTIONS(description=null)"

Expand Down Expand Up @@ -1030,6 +1030,14 @@ def __init__(

@classmethod
def dbapi(cls):
"""
Use `import_dbapi()` instead.
Maintained for backward compatibility.
"""
return dbapi

@classmethod
def import_dbapi(cls):
return dbapi

@staticmethod
Expand Down Expand Up @@ -1202,7 +1210,21 @@ def _get_table(self, connection, table_name, schema=None):
raise NoSuchTableError(table_name)
return table

def has_table(self, connection, table_name, schema=None):
def has_table(self, connection, table_name, schema=None, **kw):
"""Checks whether a table exists in BigQuery.

Args:
connection (google.cloud.bigquery.client.Client): The client
object used to interact with BigQuery.
table_name (str): The name of the table to check for.
schema (str, optional): The name of the schema to which the table
belongs. Defaults to the default schema.
**kw (dict): Any extra keyword arguments will be ignored.

Returns:
bool: True if the table exists, False otherwise.

"""
try:
self._get_table(connection, table_name, schema)
return True
Expand Down Expand Up @@ -1279,7 +1301,13 @@ def __init__(self, *args, **kwargs):
raise TypeError("The unnest function requires a single argument.")
arg = args[0]
if isinstance(arg, sqlalchemy.sql.expression.ColumnElement):
if not isinstance(arg.type, sqlalchemy.sql.sqltypes.ARRAY):
if not (
isinstance(arg.type, sqlalchemy.sql.sqltypes.ARRAY)
or (
hasattr(arg.type, "impl")
and isinstance(arg.type.impl, sqlalchemy.sql.sqltypes.ARRAY)
)
):
raise TypeError("The argument to unnest must have an ARRAY type.")
self.type = arg.type.item_type
super().__init__(*args, **kwargs)
Expand Down
6 changes: 6 additions & 0 deletions sqlalchemy_bigquery/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import sqlalchemy.testing.requirements
import sqlalchemy.testing.exclusions
from sqlalchemy.testing.exclusions import against, only_on

supported = sqlalchemy.testing.exclusions.open
unsupported = sqlalchemy.testing.exclusions.closed
Expand Down Expand Up @@ -136,6 +137,11 @@ def schemas(self):

return unsupported()

@property
def array_type(self):
"""Target database must support array_type"""
return supported()

@property
def implicit_default_schema(self):
"""target system has a strong concept of 'default' schema that can
Expand Down
12 changes: 0 additions & 12 deletions testing/constraints-3.7.txt

This file was deleted.

Loading