Core: Make psycopg (v3) the default synchronous Postgres driver#69469
Open
Dev-iL wants to merge 1 commit into
Open
Core: Make psycopg (v3) the default synchronous Postgres driver#69469Dev-iL wants to merge 1 commit into
Dev-iL wants to merge 1 commit into
Conversation
1cde217 to
5cafc42
Compare
1 task
23a8aa8 to
43d100e
Compare
Mirrors the async default switch (apache#67801/apache#68496): a bare postgresql:// or legacy postgres:// / postgres+psycopg2:// sql_alchemy_conn is now rewritten to postgresql+psycopg:// instead of postgresql+psycopg2:// when the psycopg (v3) package is installed, falling back to postgresql+psycopg2:// otherwise so environments without psycopg3 keep working. The psycopg2-specific executemany tuning in prepare_engine_args now follows the actually-configured driver instead of whether psycopg happens to be importable. An explicit postgresql+psycopg2:// URL is never rewritten. Also updates hardcoded postgresql+psycopg2:// example connection strings across docs and reference deployment configs that would otherwise break for anyone following them as-is now that the postgres provider no longer bundles psycopg2-binary by default. Part of the migration tracked in apache#68453.
43d100e to
91b2aa8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
related:
Mirrors the async default switch: a bare
postgresql://or legacypostgres:///postgres+psycopg2://sql_alchemy_connis now rewritten topostgresql+psycopg://instead ofpostgresql+psycopg2://. An explicitpostgresql+psycopg2://URL is never rewritten and keeps working as long asapache-airflow-providers-postgres[psycopg2]is installed.This PR touches core only - there will be separate PRs for providers.
What changed
_upgrade_postgres_metastore_conn(airflow-core/src/airflow/configuration.py):good_schemechanged frompostgresql+psycopg2topostgresql+psycopgwhen thepsycopg(v3) package is actually importable (find_spec("psycopg") is not None), mirroring the exact guardsettings.pyalready uses for the async driver default (_USE_PSYCOPG3); falls back topostgresql+psycopg2otherwise, so environments that don't have psycopg3 installed (e.g. a min-supported-Airflow-version test matrix, or a provider release pinned before psycopg3 support existed) keep working instead of hittingsqlalchemy.exc.NoSuchModuleError.bad_schemesis unchanged.prepare_engine_args(airflow-core/src/airflow/settings.py): the psycopg2-specificexecutemany_mode/executemany_batch_page_sizetuning now follows the actually-configuredSQL_ALCHEMY_CONNscheme, not whether thepsycopgpackage happens to be importable.airflow-core/src/airflow/migrations/versions/0017_2_9_2_fix_inconsistency_between_ORM_and_migration_files.py:literal("0")(a Pythonstr) bound to theIntegercolumndag.max_consecutive_failed_dag_runs. psycopg2 let Postgres coerce the untyped bind param silently; psycopg3's dialect renders it with an explicit::VARCHARcast, causingpsycopg.errors.DatatypeMismatchon every fresh migration. Changed toliteral(0).airflow-core/src/airflow/migrations/versions/0094_3_2_0_replace_deadline_inline_callback_with_fkey.py: the:prefix/:classnamestring bind params were passed directly intojson_build_object(...), a polymorphic"any"-argument function. psycopg3 uses the extended query protocol, so Postgres must resolve each parameter's type atParsetime and can't for polymorphic functions without an explicit cast, raisingpsycopg.errors.IndeterminateDatatype. Fixed by wrapping both bind sites (upgrade and downgrade) inCAST(... AS text).scripts/ci/docker-compose/backend-postgres.yml:AIRFLOW__DATABASE__SQL_ALCHEMY_CONNreverted from a hardcodedpostgresql+psycopg://...back to the barepostgresql://...it was before this PR's docs/config sweep. This file is shared by every Postgres-backed CI job, including the "Migration Tests" job that runs against an old released Airflow (--use-airflow-version <MIN_AIRFLOW_VERSION>, currently 2.11.0) with--mount-sources skip— i.e. it never loads this branch'sconfiguration.pyat all. Hardcoding the new+psycopgscheme meant that job handedcreate_enginean explicit driver it doesn't have installed, causingsqlalchemy.exc.NoSuchModuleError: postgresql.psycopg, which thefind_specguard above can't fix since it doesn't run in that job. Reverting to barepostgresql://lets each Airflow version's own scheme-upgrade logic pick a driver it actually has (+psycopgon this branch,+psycopg2on 2.11.0).AIRFLOW__CELERY__RESULT_BACKENDis intentionally left as explicit+psycopg, since that line only affects the celery-specific integration job, which always runs current-branch code where psycopg3 is guaranteed present.airflow-core/src/airflow/models/serialized_dag.py::get_dag_dependencies: the Postgres#>path-extraction operator (jsonb #> text[]) was fed an untyped Python string bind param shaped like a Postgres array literal ('{"dag","dag_dependencies"}'). psycopg2 let Postgres infer/cast it totext[]implicitly; psycopg3 renders it ascharacter varying, which doesn't match the operator's signature, raisingpsycopg.errors.UndefinedFunction. Fixed by passing an explicitliteral(["dag", "dag_dependencies"], type_=ARRAY(String))instead of a string literal, so the array type is unambiguous regardless of driver — this broke theui/dependenciesAPI route and every test depending on it (test_serialized_dag.py,test_dependencies.py,test_structure.py,test_dag_command.py'sshow-dependenciesCLI tests).airflow-core/tests/unit/api_fastapi/common/test_exceptions.py: two tests asserted on the exact rendered SQL statement and driver error text embedded in aIntegrityError→HTTPExceptionconversion. psycopg3 adds explicit::VARCHAR/::INTEGERcasts to bind params in the rendered statement (psycopg2 doesn't) and drops the trailing\npsycopg2 leaves on Postgres'sDETAIL:error line. Since the sibling multi-column test already established the right pattern for this ("the SQL statement is an implementation detail, so we match on the statement pattern instead of an exact match"), applied the same treatment to the single-column test (popstatement, match on target table) and added.rstrip("\n")normalization fororig_errorto both tests, since that trailing whitespace is equally a driver-rendering detail, not meaningful content.airflow-core/tests/unit/core/test_configuration.py::test_upgrade_postgres_metastore_conn: this test asserted the real, ambientfind_spec("psycopg")result rather than mocking it, so it silently depended on whatever the CI job's actual environment happened to have installed — it passed on the standard test image but failed on the "Low dep tests: core" job (which doesn't install psycopg3 for airflow-core's own test env). Added the samefind_specmock the sibling_without_psycopg3test already uses, making the psycopg3-available assertion deterministic instead of environment-dependent.airflow-core/docs/howto/set-up-database.rst, mirroring the existing async section, documenting the new default and how to restore psycopg2 via the provider's[psycopg2]extra.postgresql+psycopg2://example connection strings across docs and reference deployment configs (Docker Compose quick-start, Helm chart values, CI backend config, task-sdk integration tests, a logging/multi-team doc) that would otherwise break for anyone following them as-is now that the postgres provider no longer bundles psycopg2-binary by default. (The same fix forproviders/amazon's executor docs is split into a separate psycopg3-sync-amazon-docs PR, since it's outsideairflow-core.)airflow-core/newsfragments/69469.improvement.rst.Test plan
airflow-core/tests/unit/core/test_configuration.py::test_upgrade_postgres_metastore_connupdated to assertpostgresql+psycopg, plus a case confirming an explicitpostgresql+psycopg2://value is left untouched.airflow-core/tests/unit/core/test_configuration.py::test_upgrade_postgres_metastore_conn_without_psycopg3: new test mockingfind_specabsent, confirming the fallback topostgresql+psycopg2.airflow-core/tests/unit/core/test_sqlalchemy_config.py: new parametrized test covering all four combinations of{postgresql+psycopg2, postgresql+psycopg} × {psycopg3 available, not available}, confirming the executemany tuning follows the configured scheme rather than package importability.sql_alchemy_connvalues with no live Postgres required.airflow db reset && airflow db migrate --to-revision heads, run by every Postgres-backed core/integration/special-test job) — these were the actual jobs failing on CI before this fix. Additionally verified locally: a fulldb migrate --to-revision heads→db downgrade→db migrate --to-revision headscycle against a live breeze Postgres, withpostgresql+psycopgas the driver, completes cleanly.backend-postgres.ymlrevert was verified by reproducing the exact failing "Migration Tests" CI job locally (breeze shell --backend postgres --mount-sources skip --python 3.10 --use-airflow-version 2.11.0 'airflow db reset --skip-init -y && airflow db migrate --to-revision heads'), which failed withNoSuchModuleErrorbefore the fix and completes cleanly (Database migrating done!) after it.test_serialized_dag.py::test_get_dependencies,test_dependencies.py::TestGetDependencies::test_should_response_200, andtest_exceptions.py::TestUniqueConstraintErrorHandlerverified against a live breeze Postgres (postgresql+psycopgdriver) — all failed before these last two fixes and pass after.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Sonnet 5 following the guidelines
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.