René's URL Explorer Experiment


Title: Support automatic translation of unique=True / UniqueConstraint to unique indexes in migrations · Issue #765 · googleapis/python-spanner-sqlalchemy · GitHub

Open Graph Title: Support automatic translation of unique=True / UniqueConstraint to unique indexes in migrations · Issue #765 · googleapis/python-spanner-sqlalchemy

X Title: Support automatic translation of unique=True / UniqueConstraint to unique indexes in migrations · Issue #765 · googleapis/python-spanner-sqlalchemy

Description: Is your feature request related to a problem? Please describe. Yes. And relates to #228 The Cloud Spanner SQLAlchemy dialect (python-spanner-sqlalchemy) does not support table-level UNIQUE constraints, while the idiomatic SQLAlchemy/SQLM...

Open Graph Description: Is your feature request related to a problem? Please describe. Yes. And relates to #228 The Cloud Spanner SQLAlchemy dialect (python-spanner-sqlalchemy) does not support table-level UNIQUE constrai...

X Description: Is your feature request related to a problem? Please describe. Yes. And relates to #228 The Cloud Spanner SQLAlchemy dialect (python-spanner-sqlalchemy) does not support table-level UNIQUE constrai...

Opengraph URL: https://github.com/googleapis/python-spanner-sqlalchemy/issues/765

X: @github

direct link

Domain: patch-diff.githubusercontent.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Support automatic translation of unique=True / UniqueConstraint to unique indexes in migrations","articleBody":"## **Is your feature request related to a problem? Please describe.**\n\nYes.  And relates to #228 \n\nThe Cloud Spanner SQLAlchemy dialect (`python-spanner-sqlalchemy`) does not support table-level `UNIQUE` constraints, while the idiomatic SQLAlchemy/SQLModel way to express uniqueness is `unique=True` on a `Column` or a `UniqueConstraint` in `__table_args__`.\n\nWhen running Alembic autogenerate or migrations against Spanner, these become `CREATE UNIQUE CONSTRAINT` or `ADD CONSTRAINT ... UNIQUE` operations that fail, since Spanner does not support table-level unique constraints.  \n\nThis is common for teams sharing models across Postgres/SQLite/Spanner or migrating to Spanner. The result is friction, boilerplate workarounds, and dialect-specific migration hacks.\n\n---\n\n## **Describe the solution you'd like**\n\nPlease add native support to automatically **translate `UNIQUE` constraints into unique indexes** for Spanner.  \n\nConcretely:\n\n- When Alembic autogenerates migrations for Spanner, emit `op.create_index(..., unique=True)` instead of `op.create_unique_constraint(...)`.\n- When running migrations containing `CreateUniqueConstraintOp` / `AddConstraintOp(UniqueConstraint)` / `DropConstraintOp(unique)`, transparently execute `CREATE UNIQUE INDEX` / `DROP INDEX`.\n- Optionally, add a flag like `spanner_enforce_unique_via_index=True` to control this behavior.\n\nThis would let models using `unique=True` or `UniqueConstraint` work on Spanner without any schema or migration rewrites.\n\n---\n\n## **Describe alternatives you've considered**\n\n1. **Custom Alembic hooks / dialect patches:**  \n   - Developers can use `process_revision_directives` or custom Alembic `renderers` to rewrite unique constraints into unique indexes manually.  \n   - This works but requires significant boilerplate (that should be in this repository to be reused).\n\n2. **Dialect-specific model changes:**  \n   - Removing `unique=True` or duplicating with explicit `Index(..., unique=True)` definitions.  \n   - This breaks cross-dialect compatibility and influences peoples models too much to be generic.\n\n3. **Ignoring uniqueness enforcement on Spanner:**  \n   - Skipping constraints entirely is unsafe, as uniqueness violations go undetected.\n   - This is probably the simplest and better than just exploding people's migrations so long as a warning is emitted.\n\n---\n\n## **Additional context**\n\nSpanner supports **unique indexes** but not **unique constraints**.  \nSupporting this translation natively would make cross-dialect ORM models compatible without user intervention and would align Spanner’s dialect with the expectations of the broader SQLAlchemy ecosystem.\n\n### Minimal (Vibed from my code pasted in #228) Proof of Concept\n\nBelow is a minimal example using Alembic’s renderer dispatch to transparently rewrite unique constraints into unique indexes **only for Spanner**.\n\n```python\n# spanner_renderers.py\nfrom alembic.autogenerate import renderers\nfrom alembic.autogenerate.api import AutogenContext\nfrom alembic.operations import ops\nfrom sqlalchemy.sql.schema import UniqueConstraint\n\ndef _mk_idx_name(table: str, cols, name: str | None) -\u003e str:\n    return name or f\"uq_{table}_{'_'.join(cols)}\"\n\ndef _render_create_idx(table: str, cols, name: str | None, schema: str | None) -\u003e str:\n    idx_name = _mk_idx_name(table, cols, name)\n    parts = [repr(idx_name), repr(table), repr(cols), \"unique=True\"]\n    if schema:\n        parts.append(f\"schema={schema!r}\")\n    return f\"op.create_index({', '.join(parts)})\"\n\ndef _render_drop_idx(table: str, name: str, schema: str | None) -\u003e str:\n    parts = [repr(name), f\"table_name={table!r}\"]\n    if schema:\n        parts.append(f\"schema={schema!r}\")\n    return f\"op.drop_index({', '.join(parts)})\"\n\n# CreateUniqueConstraintOp → create unique index (Spanner)\n@renderers.dispatch_for(ops.CreateUniqueConstraintOp, \"spanner\")\ndef _render_create_uc_spanner(autogen_context: AutogenContext, op: ops.CreateUniqueConstraintOp) -\u003e str:\n    return _render_create_idx(op.table_name, list(op.columns), op.constraint_name, op.schema)\n\n# AddConstraintOp(UniqueConstraint(...)) → create unique index (Spanner)\n@renderers.dispatch_for(ops.AddConstraintOp, \"spanner\")\ndef _render_add_uc_spanner(autogen_context: AutogenContext, op: ops.AddConstraintOp) -\u003e str:\n    cons = op.constraint\n    if isinstance(cons, UniqueConstraint):\n        cols = [c.name for c in cons.columns]\n        return _render_create_idx(cons.table.name, cols, cons.name, cons.table.schema)\n    raise NotImplementedError\n\n# DropConstraintOp(unique) → drop index (Spanner)\n@renderers.dispatch_for(ops.DropConstraintOp, \"spanner\")\ndef _render_drop_uc_spanner(autogen_context: AutogenContext, op: ops.DropConstraintOp) -\u003e str:\n    if op.constraint_type == \"unique\":\n        return _render_drop_idx(op.table_name, op.constraint_name, op.schema)\n    raise NotImplementedError","author":{"url":"https://github.com/MattOates","@type":"Person","name":"MattOates"},"datePublished":"2025-10-06T16:46:16.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/765/python-spanner-sqlalchemy/issues/765"}

route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:e69c0537-32d8-a057-0ea0-c40aed576d51
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-idC3FC:162063:5A9100E:7B56EEE:6978415E
html-safe-nonce0113bf791bb95358eac5ecea353e6ffbd82566c54f84ff6efe1f84d562cd3d7a
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDM0ZDOjE2MjA2Mzo1QTkxMDBFOjdCNTZFRUU6Njk3ODQxNUUiLCJ2aXNpdG9yX2lkIjoiOTE4MTQyOTI5MDY4MjA0MDY3MCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9
visitor-hmac504786385492cd6c09db045a10c695710c6cb57f21416a44feead3440e5a564e
hovercard-subject-tagissue:3488169732
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/googleapis/python-spanner-sqlalchemy/765/issue_layout
twitter:imagehttps://opengraph.githubassets.com/22a2ff0cbffaa9d2a5b603bf254f40039e31760dbc29987ab8252bd92160cb46/googleapis/python-spanner-sqlalchemy/issues/765
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/22a2ff0cbffaa9d2a5b603bf254f40039e31760dbc29987ab8252bd92160cb46/googleapis/python-spanner-sqlalchemy/issues/765
og:image:altIs your feature request related to a problem? Please describe. Yes. And relates to #228 The Cloud Spanner SQLAlchemy dialect (python-spanner-sqlalchemy) does not support table-level UNIQUE constrai...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernameMattOates
hostnamegithub.com
expected-hostnamegithub.com
None2981c597c945c1d90ac6fa355ce7929b2f413dfe7872ca5c435ee53a24a1de50
turbo-cache-controlno-preview
go-importgithub.com/googleapis/python-spanner-sqlalchemy git https://github.com/googleapis/python-spanner-sqlalchemy.git
octolytics-dimension-user_id16785467
octolytics-dimension-user_logingoogleapis
octolytics-dimension-repository_id335511641
octolytics-dimension-repository_nwogoogleapis/python-spanner-sqlalchemy
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id335511641
octolytics-dimension-repository_network_root_nwogoogleapis/python-spanner-sqlalchemy
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release520b65a872113b919c1bbdb03834a50af15859fd
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues/765#start-of-content
https://patch-diff.githubusercontent.com/
Sign in https://patch-diff.githubusercontent.com/login?return_to=https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-spanner-sqlalchemy%2Fissues%2F765
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub SparkBuild and deploy intelligent appshttps://github.com/features/spark
GitHub ModelsManage and compare promptshttps://github.com/features/models
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://patch-diff.githubusercontent.com/login?return_to=https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-spanner-sqlalchemy%2Fissues%2F765
Sign up https://patch-diff.githubusercontent.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=googleapis%2Fpython-spanner-sqlalchemy
Reloadhttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues/765
Reloadhttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues/765
Reloadhttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues/765
googleapis https://patch-diff.githubusercontent.com/googleapis
python-spanner-sqlalchemyhttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy
Notifications https://patch-diff.githubusercontent.com/login?return_to=%2Fgoogleapis%2Fpython-spanner-sqlalchemy
Fork 33 https://patch-diff.githubusercontent.com/login?return_to=%2Fgoogleapis%2Fpython-spanner-sqlalchemy
Star 52 https://patch-diff.githubusercontent.com/login?return_to=%2Fgoogleapis%2Fpython-spanner-sqlalchemy
Code https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy
Issues 14 https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues
Pull requests 17 https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/pulls
Actions https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/actions
Projects 0 https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/projects
Security 0 https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/security
Insights https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/pulse
Code https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy
Issues https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues
Pull requests https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/pulls
Actions https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/actions
Projects https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/projects
Security https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/security
Insights https://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/pulse
New issuehttps://patch-diff.githubusercontent.com/login?return_to=https://github.com/googleapis/python-spanner-sqlalchemy/issues/765
New issuehttps://patch-diff.githubusercontent.com/login?return_to=https://github.com/googleapis/python-spanner-sqlalchemy/issues/765
Support automatic translation of unique=True / UniqueConstraint to unique indexes in migrationshttps://patch-diff.githubusercontent.com/googleapis/python-spanner-sqlalchemy/issues/765#top
https://patch-diff.githubusercontent.com/olavloite
api: spannerIssues related to the googleapis/python-spanner-sqlalchemy API.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22api%3A%20spanner%22
priority: p3Desirable enhancement or fix. May not be included in next release.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22priority%3A%20p3%22
type: feature request‘Nice-to-have’ improvement, new feature or different behavior or design.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22type%3A%20feature%20request%22
https://github.com/MattOates
https://github.com/MattOates
MattOateshttps://github.com/MattOates
on Oct 6, 2025https://github.com/googleapis/python-spanner-sqlalchemy/issues/765#issue-3488169732
#228https://github.com/googleapis/python-spanner-sqlalchemy/issues/228
#228https://github.com/googleapis/python-spanner-sqlalchemy/issues/228
olavloitehttps://patch-diff.githubusercontent.com/olavloite
api: spannerIssues related to the googleapis/python-spanner-sqlalchemy API.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22api%3A%20spanner%22
priority: p3Desirable enhancement or fix. May not be included in next release.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22priority%3A%20p3%22
type: feature request‘Nice-to-have’ improvement, new feature or different behavior or design.https://github.com/googleapis/python-spanner-sqlalchemy/issues?q=state%3Aopen%20label%3A%22type%3A%20feature%20request%22
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.