diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c817a69c01595..f50ca985e78ce 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -205,6 +205,14 @@ repos:
files: ^airflow/models/taskinstance.py$|^airflow/models/taskinstancehistory.py$
pass_filenames: false
require_serial: true
+ - id: prevent-usage-of-session.query
+ name: Prevent usage of session.query
+ entry: ./scripts/ci/pre_commit/usage_session_query.py
+ language: python
+ additional_dependencies: ['rich>=12.4.4']
+ files: ^airflow.*\.py$|^task_sdk.*\.py
+ exclude: ^tests/.*\.py$|^task_sdk/tests/.*\.py$
+ pass_filenames: true
- id: check-deferrable-default
name: Check and fix default value of default_deferrable
language: python
diff --git a/contributing-docs/08_static_code_checks.rst b/contributing-docs/08_static_code_checks.rst
index 8eccb20a304ff..fd73efd6f86c1 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -336,6 +336,8 @@ require Breeze Docker image to be built locally.
+-----------------------------------------------------------+--------------------------------------------------------+---------+
| pretty-format-json | Format JSON files | |
+-----------------------------------------------------------+--------------------------------------------------------+---------+
+| prevent-usage-of-session.query | Prevent usage of session.query | |
++-----------------------------------------------------------+--------------------------------------------------------+---------+
| pylint | pylint | |
+-----------------------------------------------------------+--------------------------------------------------------+---------+
| python-no-log-warn | Check if there are no deprecate log warn | |
diff --git a/dev/breeze/doc/images/output_static-checks.svg b/dev/breeze/doc/images/output_static-checks.svg
index ecdf3f100ca25..fbeaedcf36e09 100644
--- a/dev/breeze/doc/images/output_static-checks.svg
+++ b/dev/breeze/doc/images/output_static-checks.svg
@@ -364,48 +364,49 @@
│fix-encoding-pragma | flynt | generate-airflow-diagrams | generate-openapi-spec |│
│generate-pypi-readme | generate-tasksdk-datamodels | generate-volumes-for-sources│
│| identity | insert-license | kubeconform | lint-chart-schema | lint-dockerfile |│
-│lint-helm-chart | lint-json-schema | lint-markdown | mixed-line-ending | │
-│mypy-airflow | mypy-dev | mypy-docs | mypy-providers | mypy-task-sdk | │
-│pretty-format-json | pylint | python-no-log-warn | replace-bad-characters | │
-│rst-backticks | ruff | ruff-format | shellcheck | trailing-whitespace | │
-│ts-compile-format-lint-ui | update-black-version | update-breeze-cmd-output | │
-│update-breeze-readme-config-hash | update-chart-dependencies | update-er-diagram │
-│| update-extras | update-in-the-wild-to-be-sorted | │
-│update-inlined-dockerfile-scripts | update-installed-providers-to-be-sorted | │
-│update-installers-and-pre-commit | update-local-yml-file | │
-│update-migration-references | update-providers-build-files | │
-│update-providers-dependencies | update-reproducible-source-date-epoch | │
-│update-spelling-wordlist-to-be-sorted | update-supported-versions | │
-│update-vendored-in-k8s-json-schema | update-version | validate-operators-init | │
-│yamllint | zizmor) │
-│--show-diff-on-failure-sShow diff for files modified by the checks.│
-│--initialize-environmentInitialize environment before running checks.│
-│--max-initialization-attemptsMaximum number of attempts to initialize environment before giving up.│
-│(INTEGER RANGE) │
-│[default: 3; 1<=x<=10] │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-╭─ Selecting files to run the checks on ───────────────────────────────────────────────────────────────────────────────╮
-│--file-fList of files to run the checks on.(PATH)│
-│--all-files-aRun checks on all files.│
-│--commit-ref-rRun checks for this commit reference only (can be any git commit-ish reference). Mutually │
-│exclusive with --last-commit. │
-│(TEXT) │
-│--last-commit-cRun checks for all files in last commit. Mutually exclusive with --commit-ref.│
-│--only-my-changes-mRun checks for commits belonging to my PR only: for all commits between merge base to `main` │
-│branch and HEAD of your branch. │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-╭─ Building image before running checks ───────────────────────────────────────────────────────────────────────────────╮
-│--skip-image-upgrade-checkSkip checking if the CI image is up to date.│
-│--force-buildForce image build no matter if it is determined as needed.│
-│--github-repository-gGitHub repository used to pull, push run images.(TEXT)[default: apache/airflow]│
-│--builderBuildx builder used to perform `docker buildx build` commands.(TEXT)│
-│[default: autodetect] │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-╭─ Common options ─────────────────────────────────────────────────────────────────────────────────────────────────────╮
-│--dry-run-DIf dry-run is set, commands are only printed, not executed.│
-│--verbose-vPrint verbose information about performed steps.│
-│--help-hShow this message and exit.│
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+│lint-helm-chart | lint-json-schema | lint-markdown | lint-openapi | │
+│mixed-line-ending | mypy-airflow | mypy-dev | mypy-docs | mypy-providers | │
+│mypy-task-sdk | pretty-format-json | prevent-usage-of-session.query | pylint | │
+│python-no-log-warn | replace-bad-characters | rst-backticks | ruff | ruff-format │
+│| shellcheck | trailing-whitespace | ts-compile-format-lint-ui | │
+│update-black-version | update-breeze-cmd-output | │
+│update-breeze-readme-config-hash | update-chart-dependencies | update-er-diagram │
+│| update-extras | update-in-the-wild-to-be-sorted | │
+│update-inlined-dockerfile-scripts | update-installed-providers-to-be-sorted | │
+│update-installers-and-pre-commit | update-local-yml-file | │
+│update-migration-references | update-openapi-spec-tags-to-be-sorted | │
+│update-providers-build-files | update-providers-dependencies | │
+│update-reproducible-source-date-epoch | update-spelling-wordlist-to-be-sorted | │
+│update-supported-versions | update-vendored-in-k8s-json-schema | update-version |│
+│validate-operators-init | yamllint | zizmor) │
+│--show-diff-on-failure-sShow diff for files modified by the checks.│
+│--initialize-environmentInitialize environment before running checks.│
+│--max-initialization-attemptsMaximum number of attempts to initialize environment before giving up.│
+│(INTEGER RANGE) │
+│[default: 3; 1<=x<=10] │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Selecting files to run the checks on ───────────────────────────────────────────────────────────────────────────────╮
+│--file-fList of files to run the checks on.(PATH)│
+│--all-files-aRun checks on all files.│
+│--commit-ref-rRun checks for this commit reference only (can be any git commit-ish reference). Mutually │
+│exclusive with --last-commit. │
+│(TEXT) │
+│--last-commit-cRun checks for all files in last commit. Mutually exclusive with --commit-ref.│
+│--only-my-changes-mRun checks for commits belonging to my PR only: for all commits between merge base to `main` │
+│branch and HEAD of your branch. │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Building image before running checks ───────────────────────────────────────────────────────────────────────────────╮
+│--skip-image-upgrade-checkSkip checking if the CI image is up to date.│
+│--force-buildForce image build no matter if it is determined as needed.│
+│--github-repository-gGitHub repository used to pull, push run images.(TEXT)[default: apache/airflow]│
+│--builderBuildx builder used to perform `docker buildx build` commands.(TEXT)│
+│[default: autodetect] │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Common options ─────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│--dry-run-DIf dry-run is set, commands are only printed, not executed.│
+│--verbose-vPrint verbose information about performed steps.│
+│--help-hShow this message and exit.│
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
diff --git a/dev/breeze/doc/images/output_static-checks.txt b/dev/breeze/doc/images/output_static-checks.txt
index b92b4de9e174c..7be097817adde 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-8e8c3bb6193f3dbad7af5cae63d28145
+d1ee6181aeefcf228e9f2f37055a98d9
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index a303d9dd262e6..6ae9fd5ec0e3a 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -123,6 +123,7 @@
"mypy-providers",
"mypy-task-sdk",
"pretty-format-json",
+ "prevent-usage-of-session.query",
"pylint",
"python-no-log-warn",
"replace-bad-characters",
diff --git a/scripts/ci/pre_commit/usage_session_query.py b/scripts/ci/pre_commit/usage_session_query.py
new file mode 100755
index 0000000000000..cf8cd10957eab
--- /dev/null
+++ b/scripts/ci/pre_commit/usage_session_query.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import ast
+import sys
+from pathlib import Path
+
+from rich.console import Console
+
+console = Console(color_system="standard", width=200)
+
+
+def check_session_query(mod: ast.Module) -> int:
+ errors = 0
+ for node in ast.walk(mod):
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
+ if (
+ node.func.attr == "query"
+ and isinstance(node.func.value, ast.Name)
+ and node.func.value.id == "session"
+ ):
+ console.print(
+ f"\nUse of legacy `session.query` detected on line {node.lineno}. "
+ f"\nSQLAlchemy 2.0 deprecates the `Query` object"
+ f"use the `select()` construct instead."
+ )
+ errors += 1
+ return errors
+
+
+def main():
+ for file in sys.argv[1:]:
+ file_path = Path(file)
+ ast_module = ast.parse(file_path.read_text(encoding="utf-8"), file)
+ errors = check_session_query(ast_module)
+ return 1 if errors > 0 else 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())