This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 2bf3af0a656 Remove deprecated hook code from plugins (#43291)
2bf3af0a656 is described below

commit 2bf3af0a65631bb6b9271fca8a0c6724c996393c
Author: Kaxil Naik <[email protected]>
AuthorDate: Wed Oct 23 17:24:51 2024 +0100

    Remove deprecated hook code from plugins (#43291)
    
    We had removed registering hooks via plugin in 
https://github.com/apache/airflow/pull/12108 for Airflow 2.0. We kept it for 
registering Connection form.
    
    As I understand it, we don't use that code but `ProvidersManager` 
(`airflow/providers_manager.py`) to register Connections from providers.
---
 .../api_fastapi/core_api/openapi/v1-generated.yaml |  6 -----
 .../api_fastapi/core_api/serializers/plugins.py    |  1 -
 airflow/plugins_manager.py                         | 14 ++---------
 airflow/ui/openapi-gen/requests/schemas.gen.ts     |  8 ------
 airflow/ui/openapi-gen/requests/types.gen.ts       |  1 -
 .../authoring-and-scheduling/plugins.rst           |  8 ------
 newsfragments/43291.significant.rst                | 16 ++++++++++++
 tests/always/test_connection.py                    | 29 ----------------------
 .../endpoints/test_plugin_endpoint.py              |  6 -----
 tests/api_connexion/schemas/test_plugin_schema.py  |  8 ------
 tests/cli/commands/test_plugins_command.py         | 20 +++++++++------
 tests/plugins/test_plugin.py                       |  9 -------
 tests/plugins/test_plugins_manager.py              | 24 ------------------
 tests_common/test_utils/mock_plugins.py            |  2 --
 14 files changed, 30 insertions(+), 122 deletions(-)

diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml 
b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index 3d890d4da31..5eb7aed7705 100644
--- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -2207,11 +2207,6 @@ components:
         name:
           type: string
           title: Name
-        hooks:
-          items:
-            type: string
-          type: array
-          title: Hooks
         macros:
           items:
             type: string
@@ -2268,7 +2263,6 @@ components:
       type: object
       required:
       - name
-      - hooks
       - macros
       - flask_blueprints
       - fastapi_apps
diff --git a/airflow/api_fastapi/core_api/serializers/plugins.py 
b/airflow/api_fastapi/core_api/serializers/plugins.py
index 68bc8ea443c..e16b56a6aca 100644
--- a/airflow/api_fastapi/core_api/serializers/plugins.py
+++ b/airflow/api_fastapi/core_api/serializers/plugins.py
@@ -64,7 +64,6 @@ class PluginResponse(BaseModel):
     """Plugin serializer."""
 
     name: str
-    hooks: list[str]
     macros: list[str]
     flask_blueprints: list[str]
     fastapi_apps: list[FastAPIAppResponse]
diff --git a/airflow/plugins_manager.py b/airflow/plugins_manager.py
index fc7adc5993f..881e07a81ed 100644
--- a/airflow/plugins_manager.py
+++ b/airflow/plugins_manager.py
@@ -50,7 +50,6 @@ if TYPE_CHECKING:
     from types import ModuleType
     from typing import Generator
 
-    from airflow.hooks.base import BaseHook
     from airflow.listeners.listener import ListenerManager
     from airflow.timetables.base import Timetable
 
@@ -62,7 +61,6 @@ plugins: list[AirflowPlugin] | None = None
 loaded_plugins: set[str] = set()
 
 # Plugin components to integrate as modules
-registered_hooks: list[BaseHook] | None = None
 macros_modules: list[Any] | None = None
 
 # Plugin components to integrate directly
@@ -86,7 +84,6 @@ Used by the DAG serialization code to only allow specific 
classes to be created
 during deserialization
 """
 PLUGINS_ATTRIBUTES_TO_DUMP = {
-    "hooks",
     "macros",
     "admin_views",
     "flask_blueprints",
@@ -151,7 +148,6 @@ class AirflowPlugin:
 
     name: str | None = None
     source: AirflowPluginSource | None = None
-    hooks: list[Any] = []
     macros: list[Any] = []
     admin_views: list[Any] = []
     flask_blueprints: list[Any] = []
@@ -345,7 +341,7 @@ def ensure_plugins_loaded():
     """
     from airflow.stats import Stats
 
-    global plugins, registered_hooks
+    global plugins
 
     if plugins is not None:
         log.debug("Plugins are already loaded. Skipping.")
@@ -358,7 +354,6 @@ def ensure_plugins_loaded():
 
     with Stats.timer() as timer:
         plugins = []
-        registered_hooks = []
 
         load_plugins_from_plugin_directory()
         load_entrypoint_plugins()
@@ -366,11 +361,6 @@ def ensure_plugins_loaded():
         if not settings.LAZY_LOAD_PROVIDERS:
             load_providers_plugins()
 
-        # We don't do anything with these for now, but we want to keep track of
-        # them so we can integrate them in to the UI's Connection screens
-        for plugin in plugins:
-            registered_hooks.extend(plugin.hooks)
-
     if plugins:
         log.debug("Loading %d plugin(s) took %.2f seconds", len(plugins), 
timer.duration)
 
@@ -598,7 +588,7 @@ def get_plugin_info(attrs_to_dump: Iterable[str] | None = 
None) -> list[dict[str
             for attr in attrs_to_dump:
                 if attr in ("global_operator_extra_links", 
"operator_extra_links"):
                     info[attr] = [f"<{qualname(d.__class__)} object>" for d in 
getattr(plugin, attr)]
-                elif attr in ("macros", "timetables", "hooks", 
"priority_weight_strategies"):
+                elif attr in ("macros", "timetables", 
"priority_weight_strategies"):
                     info[attr] = [qualname(d) for d in getattr(plugin, attr)]
                 elif attr == "listeners":
                     # listeners may be modules or class instances
diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts 
b/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 356814d15d5..afd16d80409 100644
--- a/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -1326,13 +1326,6 @@ export const $PluginResponse = {
       type: "string",
       title: "Name",
     },
-    hooks: {
-      items: {
-        type: "string",
-      },
-      type: "array",
-      title: "Hooks",
-    },
     macros: {
       items: {
         type: "string",
@@ -1411,7 +1404,6 @@ export const $PluginResponse = {
   type: "object",
   required: [
     "name",
-    "hooks",
     "macros",
     "flask_blueprints",
     "fastapi_apps",
diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow/ui/openapi-gen/requests/types.gen.ts
index 40054121470..aef0f76f28e 100644
--- a/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -310,7 +310,6 @@ export type PluginCollectionResponse = {
  */
 export type PluginResponse = {
   name: string;
-  hooks: Array<string>;
   macros: Array<string>;
   flask_blueprints: Array<string>;
   fastapi_apps: Array<FastAPIAppResponse>;
diff --git a/docs/apache-airflow/authoring-and-scheduling/plugins.rst 
b/docs/apache-airflow/authoring-and-scheduling/plugins.rst
index 626f20eab1b..a46fe329da1 100644
--- a/docs/apache-airflow/authoring-and-scheduling/plugins.rst
+++ b/docs/apache-airflow/authoring-and-scheduling/plugins.rst
@@ -108,8 +108,6 @@ looks like:
     class AirflowPlugin:
         # The name of your plugin (str)
         name = None
-        # A list of class(es) derived from BaseHook
-        hooks = []
         # A list of references to inject into the macros namespace
         macros = []
         # A list of Blueprint object created from flask.Blueprint. For use 
with the flask_appbuilder based GUI
@@ -182,11 +180,6 @@ definitions in Airflow.
     from airflow.providers.amazon.aws.transfers.gcs_to_s3 import 
GCSToS3Operator
 
 
-    # Will show up in Connections screen in a future version
-    class PluginHook(BaseHook):
-        pass
-
-
     # Will show up under airflow.macros.test_plugin.plugin_macro
     # and in templates through {{ macros.test_plugin.plugin_macro }}
     def plugin_macro():
@@ -267,7 +260,6 @@ definitions in Airflow.
     # Defining the plugin class
     class AirflowTestPlugin(AirflowPlugin):
         name = "test_plugin"
-        hooks = [PluginHook]
         macros = [plugin_macro]
         flask_blueprints = [bp]
         fastapi_apps = [app_with_metadata]
diff --git a/newsfragments/43291.significant.rst 
b/newsfragments/43291.significant.rst
new file mode 100644
index 00000000000..ec7cacf6f15
--- /dev/null
+++ b/newsfragments/43291.significant.rst
@@ -0,0 +1,16 @@
+Support for adding Hooks via Airflow Plugins is removed
+
+Hooks should no longer be registered or imported via Airflow's plugin 
mechanism -- these types of classes
+are just treated as plain Python classes by Airflow, so there is no need to 
register them with Airflow.
+
+Before:
+
+.. code-block:: python
+
+  from airflow.hooks.my_plugin import MyHook
+
+You should instead import it as:
+
+.. code-block:: python
+
+  from my_plugin import MyHook
diff --git a/tests/always/test_connection.py b/tests/always/test_connection.py
index 03a8e136f2a..3cc24b6d9ce 100644
--- a/tests/always/test_connection.py
+++ b/tests/always/test_connection.py
@@ -32,7 +32,6 @@ from airflow.exceptions import AirflowException
 from airflow.hooks.base import BaseHook
 from airflow.models import Connection, crypto
 from airflow.providers.sqlite.hooks.sqlite import SqliteHook
-from airflow.providers_manager import HookInfo
 
 from tests_common.test_utils.config import conf_vars
 
@@ -835,31 +834,3 @@ class TestConnection:
         assert restored_conn.schema == conn.schema
         assert restored_conn.port == conn.port
         assert restored_conn.extra_dejson == conn.extra_dejson
-
-    def test_get_hook_not_found(self):
-        with mock.patch("airflow.providers_manager.ProvidersManager") as m:
-            m.return_value.hooks = {}
-            with pytest.raises(AirflowException, match='Unknown hook type 
"awesome-test-conn-type"'):
-                Connection(conn_type="awesome-test-conn-type").get_hook()
-
-    def test_get_hook_import_error(self, caplog):
-        with mock.patch("airflow.providers_manager.ProvidersManager") as m:
-            m.return_value.hooks = {
-                "awesome-test-conn-type": HookInfo(
-                    hook_class_name="foo.bar.AwesomeTest",
-                    connection_id_attribute_name="conn-id",
-                    package_name="test.package",
-                    hook_name="Awesome Connection",
-                    connection_type="awesome-test-conn-type",
-                    connection_testable=False,
-                )
-            }
-            caplog.clear()
-            caplog.set_level("ERROR", "airflow.models.connection")
-            with pytest.raises(ImportError):
-                Connection(conn_type="awesome-test-conn-type").get_hook()
-
-            assert caplog.messages
-            assert caplog.messages[0] == (
-                "Could not import foo.bar.AwesomeTest when discovering Awesome 
Connection test.package"
-            )
diff --git a/tests/api_connexion/endpoints/test_plugin_endpoint.py 
b/tests/api_connexion/endpoints/test_plugin_endpoint.py
index 487ba53a300..2f24347bef6 100644
--- a/tests/api_connexion/endpoints/test_plugin_endpoint.py
+++ b/tests/api_connexion/endpoints/test_plugin_endpoint.py
@@ -23,7 +23,6 @@ from fastapi import FastAPI
 from flask import Blueprint
 from flask_appbuilder import BaseView
 
-from airflow.hooks.base import BaseHook
 from airflow.plugins_manager import AirflowPlugin
 from airflow.ti_deps.deps.base_ti_dep import BaseTIDep
 from airflow.timetables.base import Timetable
@@ -37,9 +36,6 @@ from tests_common.test_utils.mock_plugins import 
mock_plugin_manager
 pytestmark = [pytest.mark.db_test, pytest.mark.skip_if_database_isolation_mode]
 
 
-class PluginHook(BaseHook): ...
-
-
 def plugin_macro(): ...
 
 
@@ -100,7 +96,6 @@ class MockPlugin(AirflowPlugin):
     appbuilder_menu_items = [appbuilder_menu_items]
     global_operator_extra_links = [MockOperatorLink()]
     operator_extra_links = [MockOperatorLink()]
-    hooks = [PluginHook]
     macros = [plugin_macro]
     ti_deps = [ti_dep]
     timetables = [CustomTimetable]
@@ -156,7 +151,6 @@ class TestGetPlugins(TestPluginsEndpoint):
                         }
                     ],
                     "global_operator_extra_links": 
[f"<{qualname(MockOperatorLink().__class__)} object>"],
-                    "hooks": [qualname(PluginHook)],
                     "macros": [qualname(plugin_macro)],
                     "operator_extra_links": 
[f"<{qualname(MockOperatorLink().__class__)} object>"],
                     "source": None,
diff --git a/tests/api_connexion/schemas/test_plugin_schema.py 
b/tests/api_connexion/schemas/test_plugin_schema.py
index 951933e9ffc..d722b52977a 100644
--- a/tests/api_connexion/schemas/test_plugin_schema.py
+++ b/tests/api_connexion/schemas/test_plugin_schema.py
@@ -26,15 +26,11 @@ from airflow.api_connexion.schemas.plugin_schema import (
     plugin_collection_schema,
     plugin_schema,
 )
-from airflow.hooks.base import BaseHook
 from airflow.plugins_manager import AirflowPlugin
 
 from tests_common.test_utils.compat import BaseOperatorLink
 
 
-class PluginHook(BaseHook): ...
-
-
 def plugin_macro(): ...
 
 
@@ -67,7 +63,6 @@ class MockPlugin(AirflowPlugin):
     appbuilder_menu_items = [appbuilder_menu_items]
     global_operator_extra_links = [MockOperatorLink()]
     operator_extra_links = [MockOperatorLink()]
-    hooks = [PluginHook]
     macros = [plugin_macro]
 
 
@@ -91,7 +86,6 @@ class TestPluginSchema(TestPluginBase):
                 {"app": app, "name": "App name", "url_prefix": "/some_prefix"},
             ],
             "global_operator_extra_links": [str(MockOperatorLink())],
-            "hooks": [str(PluginHook)],
             "macros": [str(plugin_macro)],
             "operator_extra_links": [str(MockOperatorLink())],
             "source": None,
@@ -117,7 +111,6 @@ class TestPluginCollectionSchema(TestPluginBase):
                         {"app": app, "name": "App name", "url_prefix": 
"/some_prefix"},
                     ],
                     "global_operator_extra_links": [str(MockOperatorLink())],
-                    "hooks": [str(PluginHook)],
                     "macros": [str(plugin_macro)],
                     "operator_extra_links": [str(MockOperatorLink())],
                     "source": None,
@@ -134,7 +127,6 @@ class TestPluginCollectionSchema(TestPluginBase):
                         {"app": app, "name": "App name", "url_prefix": 
"/some_prefix"},
                     ],
                     "global_operator_extra_links": [str(MockOperatorLink())],
-                    "hooks": [str(PluginHook)],
                     "macros": [str(plugin_macro)],
                     "operator_extra_links": [str(MockOperatorLink())],
                     "source": None,
diff --git a/tests/cli/commands/test_plugins_command.py 
b/tests/cli/commands/test_plugins_command.py
index c9807520e4e..993d4085fe1 100644
--- a/tests/cli/commands/test_plugins_command.py
+++ b/tests/cli/commands/test_plugins_command.py
@@ -25,8 +25,8 @@ import pytest
 
 from airflow.cli import cli_parser
 from airflow.cli.commands import plugins_command
-from airflow.hooks.base import BaseHook
 from airflow.listeners.listener import get_listener_manager
+from airflow.models.baseoperatorlink import BaseOperatorLink
 from airflow.plugins_manager import AirflowPlugin
 
 from tests.plugins.test_plugin import AirflowTestPlugin as ComplexAirflowPlugin
@@ -35,13 +35,18 @@ from tests_common.test_utils.mock_plugins import 
mock_plugin_manager
 pytestmark = [pytest.mark.db_test, pytest.mark.skip_if_database_isolation_mode]
 
 
-class PluginHook(BaseHook):
-    pass
+class AirflowNewLink(BaseOperatorLink):
+    """Operator Link for Apache Airflow Website."""
+
+    name = "airflowtestlink"
+
+    def get_link(self, operator, *, ti_key):
+        return "https://airflow.apache.org";
 
 
 class TestPlugin(AirflowPlugin):
     name = "test-plugin-cli"
-    hooks = [PluginHook]
+    global_operator_extra_links = [AirflowNewLink()]
 
 
 class TestPluginsCommand:
@@ -98,7 +103,6 @@ class TestPluginsCommand:
                     "<tests_common.test_utils.mock_operators.CustomOpLink 
object>",
                     
"<tests_common.test_utils.mock_operators.CustomBaseIndexOpLink object>",
                 ],
-                "hooks": ["tests.plugins.test_plugin.PluginHook"],
                 "listeners": [
                     "tests.listeners.empty_listener",
                     "tests.listeners.class_listener.ClassBasedListener",
@@ -129,9 +133,9 @@ class TestPluginsCommand:
         # Assert that only columns with values are displayed
         expected_output = textwrap.dedent(
             """\
-            name            | hooks
-            
================+===================================================
-            test-plugin-cli | 
tests.cli.commands.test_plugins_command.PluginHook
+            name            | global_operator_extra_links
+            
================+================================================================
+            test-plugin-cli | 
<tests.cli.commands.test_plugins_command.AirflowNewLink object>
             """
         )
         assert stdout == expected_output
diff --git a/tests/plugins/test_plugin.py b/tests/plugins/test_plugin.py
index 98f64e75456..531085ffba5 100644
--- a/tests/plugins/test_plugin.py
+++ b/tests/plugins/test_plugin.py
@@ -21,9 +21,6 @@ from fastapi import FastAPI
 from flask import Blueprint
 from flask_appbuilder import BaseView as AppBuilderBaseView, expose
 
-# Importing base classes that we need to derive
-from airflow.hooks.base import BaseHook
-
 # This is the class you derive to create a plugin
 from airflow.plugins_manager import AirflowPlugin
 from airflow.task.priority_strategy import PriorityWeightStrategy
@@ -42,11 +39,6 @@ from tests_common.test_utils.mock_operators import (
 )
 
 
-# Will show up under airflow.hooks.test_plugin.PluginHook
-class PluginHook(BaseHook):
-    pass
-
-
 # Will show up under airflow.macros.test_plugin.plugin_macro
 def plugin_macro():
     pass
@@ -115,7 +107,6 @@ class CustomPriorityWeightStrategy(PriorityWeightStrategy):
 # Defining the plugin class
 class AirflowTestPlugin(AirflowPlugin):
     name = "test_plugin"
-    hooks = [PluginHook]
     macros = [plugin_macro]
     flask_blueprints = [bp]
     fastapi_apps = [app_with_metadata]
diff --git a/tests/plugins/test_plugins_manager.py 
b/tests/plugins/test_plugins_manager.py
index 158e2040abf..e6777901b80 100644
--- a/tests/plugins/test_plugins_manager.py
+++ b/tests/plugins/test_plugins_manager.py
@@ -28,7 +28,6 @@ from unittest import mock
 
 import pytest
 
-from airflow.hooks.base import BaseHook
 from airflow.listeners.listener import get_listener_manager
 from airflow.plugins_manager import AirflowPlugin
 from airflow.utils.module_loading import qualname
@@ -181,29 +180,6 @@ class TestPluginsManager:
 
         assert caplog.record_tuples == []
 
-    def test_should_load_plugins_from_property(self, caplog):
-        class AirflowTestPropertyPlugin(AirflowPlugin):
-            name = "test_property_plugin"
-
-            @property
-            def hooks(self):
-                class TestPropertyHook(BaseHook):
-                    pass
-
-                return [TestPropertyHook]
-
-        with mock_plugin_manager(plugins=[AirflowTestPropertyPlugin()]):
-            from airflow import plugins_manager
-
-            caplog.set_level(logging.DEBUG, "airflow.plugins_manager")
-            plugins_manager.ensure_plugins_loaded()
-
-            assert "AirflowTestPropertyPlugin" in str(plugins_manager.plugins)
-            assert "TestPropertyHook" in str(plugins_manager.registered_hooks)
-
-        assert caplog.records[-1].levelname == "DEBUG"
-        assert caplog.records[-1].msg == "Loading %d plugin(s) took %.2f 
seconds"
-
     def test_loads_filesystem_plugins(self, caplog):
         from airflow import plugins_manager
 
diff --git a/tests_common/test_utils/mock_plugins.py 
b/tests_common/test_utils/mock_plugins.py
index 875a9abbd3a..0a5e199fcc0 100644
--- a/tests_common/test_utils/mock_plugins.py
+++ b/tests_common/test_utils/mock_plugins.py
@@ -23,7 +23,6 @@ from airflow import __version__ as airflow_version
 
 PLUGINS_MANAGER_NULLABLE_ATTRIBUTES = [
     "plugins",
-    "registered_hooks",
     "macros_modules",
     "admin_views",
     "flask_blueprints",
@@ -41,7 +40,6 @@ PLUGINS_MANAGER_NULLABLE_ATTRIBUTES = [
 
 PLUGINS_MANAGER_NULLABLE_ATTRIBUTES_V2_10 = [
     "plugins",
-    "registered_hooks",
     "macros_modules",
     "admin_views",
     "flask_blueprints",

Reply via email to