feat: upgrade to Flask 3.1.3 and SQLAlchemy 1.4.53#7722
Conversation
Upgrade security-critical Python dependencies while staying on Flask 2.x. Werkzeug 3.x is compatible with Flask 2.3.x and provides important security fixes without requiring a Flask 3 migration. Core dependency upgrades: - authlib: 0.15.5 → 1.7.2 (fixes CVEs in OAuth/OIDC flows) - cryptography: 43.0.1 → 48.0.0 - flask: 2.3.2 → 2.3.3 - flask-login: 0.6.0 → 0.6.3 - flask-wtf: 1.1.1 → 1.3.0 - itsdangerous: 2.1.2 → 2.2.0 - jinja2: 3.1.5 → 3.1.6 - pyjwt: 2.4.0 → 2.12.0 - pyopenssl: 24.2.1 → 26.2.0 - python-dotenv: 0.19.2 → 1.2.2 - requests: 2.32.3 → 2.33.0 - sqlparse: 0.5.0 → 0.5.4 - urllib3: 1.26.19 → 1.26.20 - werkzeug: 2.3.8 → 3.1.6 Added transitive dependency pins: pyasn1, mako, pynacl Data source dependency upgrades: - boto3/botocore: 1.28.8 → 1.43.7 - snowflake-connector-python: 3.12.3 → 4.5.0 - New additions: azure-core, grpcio, h11, httpcore, marshmallow Dev dependency upgrades: - pre-commit: 3.3.3 → 4.3.0 - Add filelock, pygments, virtualenv Code changes for authlib 1.x and Werkzeug 3.x compatibility: - Remove api_key_load_user_from_request from user_loader (belongs in request_loader) - Add TESTING-mode reset_request_g_cache hook to prevent g leakage across test requests - Pass explicit client_id/client_secret to oauth.register() (authlib 1.x API) - Replace flask.globals._app_ctx_stack with current_app (Werkzeug 3 removal) Co-authored-by: Cursor <cursoragent@cursor.com>
Migrate Redash to Flask 3.x and SQLAlchemy 1.4 with Flask-SQLAlchemy 3.0. Builds on the Python security dependency upgrades (authlib 1.7, Werkzeug 3). Dependency upgrades: - flask: 2.3.3 → 3.1.3 - flask-migrate: 2.5.2 → 4.0.7 - flask-sqlalchemy: 2.5.1 → 3.0.5 - sqlalchemy: 1.3.24 → 1.4.53 - blinker: 1.6.2 → 1.9.0 - pyjwt: 2.12.0 → 2.13.0 - supervisor: 4.1.0 → 4.3.0 - nzalchemy: ^11.0.2 → ^11.1.2 - pytest: 7.4.0 → 9.0.3, coverage: 7.14.0, pytest-cov: 6.0.0 Compatibility changes: - Flask-SQLAlchemy 3 paginate() API in handlers/base.py - SQLAlchemy 1.4 query APIs in models, metrics, query_order - SearchBaseQuery shim for sqlalchemy-searchable on SQLAlchemy 1.4 - ChangeTrackingMixin guards for detached/deleted ORM objects - Rewrite csp_allows_embeding for flask-talisman dict CSP - Soft-import supervisor_checks in cli/rq.py (no Python 3.13 wheel) - TESTING guards for session management in auth and query execution - Compact JSON in ConfigurationContainer.to_json() Test infrastructure: - pytest.ini: pythonpath, addopts, logging - tests/destinations/__init__.py for proper package collection - JWT test teardown fixes, CLI JSON expectation updates Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
3 issues found across 26 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="redash/cli/rq.py">
<violation number="1" location="redash/cli/rq.py:114">
P1: Click CLI commands do not use return values as exit codes. `return 1` in a `@manager.command()` function will be ignored and the process will exit with code 0, so monitoring scripts relying on exit status will incorrectly treat a failed healthcheck as successful.</violation>
</file>
<file name="redash/utils/query_order.py">
<violation number="1" location="redash/utils/query_order.py:104">
P1: Regression: get_mapper no longer raises on ambiguous table-to-mapper mappings</violation>
</file>
<file name="redash/models/changes.py">
<violation number="1" location="redash/models/changes.py:76">
P2: Broad `except Exception: pass` in audit change tracking can silently corrupt change history by failing to record the true previous value and providing no observability when unexpected errors occur.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| @manager.command() | ||
| def healthcheck(): | ||
| if not SUPERVISOR_CHECKS_AVAILABLE: | ||
| print("Error: supervisor_checks not available. Cannot perform healthcheck.") |
There was a problem hiding this comment.
P1: Click CLI commands do not use return values as exit codes. return 1 in a @manager.command() function will be ignored and the process will exit with code 0, so monitoring scripts relying on exit status will incorrectly treat a failed healthcheck as successful.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At redash/cli/rq.py, line 114:
<comment>Click CLI commands do not use return values as exit codes. `return 1` in a `@manager.command()` function will be ignored and the process will exit with code 0, so monitoring scripts relying on exit status will incorrectly treat a failed healthcheck as successful.</comment>
<file context>
@@ -47,47 +60,57 @@ def worker(queues):
@manager.command()
def healthcheck():
+ if not SUPERVISOR_CHECKS_AVAILABLE:
+ print("Error: supervisor_checks not available. Cannot perform healthcheck.")
+ return 1
return check_runner.CheckRunner("worker_healthcheck", "worker", None, [(WorkerHealthcheck, {})]).run()
</file context>
| for from_obj in query.statement.get_final_froms(): | ||
| if hasattr(from_obj, "left") and hasattr(from_obj, "right"): | ||
| if isinstance(from_obj.right, sa.Table): | ||
| for registry in mapperlib._mapper_registries: |
There was a problem hiding this comment.
P1: Regression: get_mapper no longer raises on ambiguous table-to-mapper mappings
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At redash/utils/query_order.py, line 104:
<comment>Regression: get_mapper no longer raises on ambiguous table-to-mapper mappings</comment>
<file context>
@@ -98,7 +95,28 @@ def get_query_entities(query):
+ for from_obj in query.statement.get_final_froms():
+ if hasattr(from_obj, "left") and hasattr(from_obj, "right"):
+ if isinstance(from_obj.right, sa.Table):
+ for registry in mapperlib._mapper_registries:
+ for mapper in registry.mappers:
+ if from_obj.right in mapper.tables:
</file context>
| try: | ||
| previous = getattr(self, attr.key, None) | ||
| self._clean_values[col.name] = previous | ||
| except Exception: |
There was a problem hiding this comment.
P2: Broad except Exception: pass in audit change tracking can silently corrupt change history by failing to record the true previous value and providing no observability when unexpected errors occur.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At redash/models/changes.py, line 76:
<comment>Broad `except Exception: pass` in audit change tracking can silently corrupt change history by failing to record the true previous value and providing no observability when unexpected errors occur.</comment>
<file context>
@@ -63,10 +63,19 @@ def prep_cleanvalues(self):
+ try:
+ previous = getattr(self, attr.key, None)
+ self._clean_values[col.name] = previous
+ except Exception:
+ # If we can't get the attribute (e.g., object deleted), skip it
+ pass
</file context>
|
This PR has been split into two smaller, more reviewable PRs:
Why the split?The key insight is that Flask-SQLAlchemy 3.0.5 requires only Flask ≥ 2.2.5 (not Flask 3). This means the ORM migration can land independently, and the Flask 3 upgrade can follow as a smaller, cleaner PR. Benefits:
Closing this PR in favor of the split approach. |
1. P1: Restore get_mapper() ambiguity check - collect all matching mappers and raise ValueError if multiple found (regression from original code) 2. P2: Log exceptions in ChangeTrackingMixin instead of silent pass - provides observability when attribute access fails during change tracking Addresses cubic-dev-ai issues on original getredash#7722. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Test plan
poetry install --only main,devmake test— expect ~900+ pytest passesNotes
This is the significant migration PR. Depends on #7719 for authlib/Werkzeug foundation.
Does not include advocate→champion (#7721) or npm upgrades (#7720).
Made with Cursor