|
15 | 15 | import re |
16 | 16 |
|
17 | 17 | import sqlalchemy |
18 | | -from alembic.ddl.base import ( |
19 | | - ColumnNullable, |
20 | | - ColumnType, |
21 | | - alter_column, |
22 | | - alter_table, |
23 | | - format_server_default, |
24 | | - format_type, |
25 | | -) |
26 | 18 | from google.api_core.client_options import ClientOptions |
27 | 19 | from google.auth.credentials import AnonymousCredentials |
28 | 20 | from google.cloud.spanner_v1 import Client, TransactionOptions |
|
51 | 43 | from google.cloud.sqlalchemy_spanner import version as sqlalchemy_spanner_version |
52 | 44 | from google.cloud.sqlalchemy_spanner._opentelemetry_tracing import trace_call |
53 | 45 |
|
| 46 | +# Defensively decouple the Alembic import so the Spanner dialect does not |
| 47 | +# hard-depend on Alembic at runtime. A database dialect does not inherently |
| 48 | +# require a schema migration tool, and consumers using only SQLAlchemy Core |
| 49 | +# or ORM (or managing DDL outside of Alembic) can still import and use the |
| 50 | +# dialect even if Alembic is omitted or unavailable in the environment |
| 51 | +# (see #18584). |
| 52 | +try: |
| 53 | + from alembic.ddl.base import ( |
| 54 | + ColumnNullable, |
| 55 | + ColumnType, |
| 56 | + alter_column, |
| 57 | + alter_table, |
| 58 | + format_server_default, |
| 59 | + format_type, |
| 60 | + ) |
| 61 | + |
| 62 | + HAS_ALEMBIC_INSTALLED = True |
| 63 | +except ImportError: |
| 64 | + HAS_ALEMBIC_INSTALLED = False |
| 65 | + |
54 | 66 | USING_SQLACLCHEMY_20 = False |
55 | 67 | if sqlalchemy.__version__.split(".")[0] == "2": |
56 | 68 | USING_SQLACLCHEMY_20 = True |
@@ -1882,55 +1894,60 @@ def do_execute_no_params(self, cursor, statement, context=None): |
1882 | 1894 | cursor.execute(statement) |
1883 | 1895 |
|
1884 | 1896 |
|
1885 | | -# Alembic ALTER operation override |
1886 | | -@compiles(ColumnNullable, "spanner+spanner") |
1887 | | -def visit_column_nullable( |
1888 | | - element: "ColumnNullable", compiler: "SpannerDDLCompiler", **kw |
1889 | | -) -> str: |
1890 | | - return _format_alter_column( |
1891 | | - compiler, |
1892 | | - element.table_name, |
1893 | | - element.schema, |
1894 | | - element.column_name, |
1895 | | - element.existing_type, |
1896 | | - element.nullable, |
1897 | | - element.existing_server_default, |
1898 | | - ) |
1899 | | - |
1900 | | - |
1901 | | -# Alembic ALTER operation override |
1902 | | -@compiles(ColumnType, "spanner+spanner") |
1903 | | -def visit_column_type( |
1904 | | - element: "ColumnType", compiler: "SpannerDDLCompiler", **kw |
1905 | | -) -> str: |
1906 | | - return _format_alter_column( |
1907 | | - compiler, |
1908 | | - element.table_name, |
1909 | | - element.schema, |
1910 | | - element.column_name, |
1911 | | - element.type_, |
1912 | | - element.existing_nullable, |
1913 | | - element.existing_server_default, |
1914 | | - ) |
| 1897 | +# Cloud Spanner requires ALTER TABLE ... ALTER COLUMN statements to specify the |
| 1898 | +# complete column definition (type, nullability, and default expression), whereas |
| 1899 | +# Alembic's default DDL compiler emits partial clauses (e.g., only SET NOT NULL |
| 1900 | +# or TYPE). Because the @compiles decorators reference Alembic's ColumnNullable |
| 1901 | +# and ColumnType classes at module import time, we only register these overrides |
| 1902 | +# when Alembic is available in the environment. |
| 1903 | +if HAS_ALEMBIC_INSTALLED: |
| 1904 | + # Alembic ALTER operation override |
| 1905 | + @compiles(ColumnNullable, "spanner+spanner") |
| 1906 | + def visit_column_nullable( |
| 1907 | + element: "ColumnNullable", compiler: "SpannerDDLCompiler", **kw |
| 1908 | + ) -> str: |
| 1909 | + return _format_alter_column( |
| 1910 | + compiler, |
| 1911 | + element.table_name, |
| 1912 | + element.schema, |
| 1913 | + element.column_name, |
| 1914 | + element.existing_type, |
| 1915 | + element.nullable, |
| 1916 | + element.existing_server_default, |
| 1917 | + ) |
1915 | 1918 |
|
| 1919 | + # Alembic ALTER operation override |
| 1920 | + @compiles(ColumnType, "spanner+spanner") |
| 1921 | + def visit_column_type( |
| 1922 | + element: "ColumnType", compiler: "SpannerDDLCompiler", **kw |
| 1923 | + ) -> str: |
| 1924 | + return _format_alter_column( |
| 1925 | + compiler, |
| 1926 | + element.table_name, |
| 1927 | + element.schema, |
| 1928 | + element.column_name, |
| 1929 | + element.type_, |
| 1930 | + element.existing_nullable, |
| 1931 | + element.existing_server_default, |
| 1932 | + ) |
1916 | 1933 |
|
1917 | | -def _format_alter_column( |
1918 | | - compiler, table_name, schema, column_name, type_, nullable, server_default |
1919 | | -): |
1920 | | - # Older versions of SQLAlchemy pass in a boolean to indicate whether there |
1921 | | - # is an existing DEFAULT constraint, instead of the actual DEFAULT constraint |
1922 | | - # expression. In those cases, we do not want to explicitly include the DEFAULT |
1923 | | - # constraint in the expression that is generated here. |
1924 | | - if isinstance(server_default, bool): |
1925 | | - server_default = None |
1926 | | - return "%s %s %s%s%s" % ( |
1927 | | - alter_table(compiler, table_name, schema), |
1928 | | - alter_column(compiler, column_name), |
1929 | | - format_type(compiler, type_), |
1930 | | - "" if nullable else " NOT NULL", |
1931 | | - ( |
1932 | | - "" |
1933 | | - if server_default is None |
1934 | | - else f" DEFAULT {format_server_default(compiler, server_default)}" |
1935 | | - ), |
1936 | | - ) |
| 1934 | + def _format_alter_column( |
| 1935 | + compiler, table_name, schema, column_name, type_, nullable, server_default |
| 1936 | + ): |
| 1937 | + # Older versions of SQLAlchemy pass in a boolean to indicate whether there |
| 1938 | + # is an existing DEFAULT constraint, instead of the actual DEFAULT constraint |
| 1939 | + # expression. In those cases, we do not want to explicitly include the DEFAULT |
| 1940 | + # constraint in the expression that is generated here. |
| 1941 | + if isinstance(server_default, bool): |
| 1942 | + server_default = None |
| 1943 | + return "%s %s %s%s%s" % ( |
| 1944 | + alter_table(compiler, table_name, schema), |
| 1945 | + alter_column(compiler, column_name), |
| 1946 | + format_type(compiler, type_), |
| 1947 | + "" if nullable else " NOT NULL", |
| 1948 | + ( |
| 1949 | + "" |
| 1950 | + if server_default is None |
| 1951 | + else f" DEFAULT {format_server_default(compiler, server_default)}" |
| 1952 | + ), |
| 1953 | + ) |
0 commit comments