base.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #
  2. # Licensed to the Apache Software Foundation (ASF) under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. The ASF licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing,
  13. # software distributed under the License is distributed on an
  14. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. # KIND, either express or implied. See the License for the
  16. # specific language governing permissions and limitations
  17. # under the License.
  18. """Base class for all hooks."""
  19. from __future__ import annotations
  20. import logging
  21. import warnings
  22. from typing import TYPE_CHECKING, Any
  23. from airflow.exceptions import RemovedInAirflow3Warning
  24. from airflow.typing_compat import Protocol
  25. from airflow.utils.log.logging_mixin import LoggingMixin
  26. if TYPE_CHECKING:
  27. from airflow.models.connection import Connection # Avoid circular imports.
  28. log = logging.getLogger(__name__)
  29. class BaseHook(LoggingMixin):
  30. """
  31. Abstract base class for hooks.
  32. Hooks are meant as an interface to
  33. interact with external systems. MySqlHook, HiveHook, PigHook return
  34. object that can handle the connection and interaction to specific
  35. instances of these systems, and expose consistent methods to interact
  36. with them.
  37. :param logger_name: Name of the logger used by the Hook to emit logs.
  38. If set to `None` (default), the logger name will fall back to
  39. `airflow.task.hooks.{class.__module__}.{class.__name__}` (e.g. DbApiHook will have
  40. *airflow.task.hooks.airflow.providers.common.sql.hooks.sql.DbApiHook* as logger).
  41. """
  42. def __init__(self, logger_name: str | None = None):
  43. super().__init__()
  44. self._log_config_logger_name = "airflow.task.hooks"
  45. self._logger_name = logger_name
  46. @classmethod
  47. def get_connections(cls, conn_id: str) -> list[Connection]:
  48. """
  49. Get all connections as an iterable, given the connection id.
  50. :param conn_id: connection id
  51. :return: array of connections
  52. """
  53. warnings.warn(
  54. "`BaseHook.get_connections` method will be deprecated in the future."
  55. "Please use `BaseHook.get_connection` instead.",
  56. RemovedInAirflow3Warning,
  57. stacklevel=2,
  58. )
  59. return [cls.get_connection(conn_id)]
  60. @classmethod
  61. def get_connection(cls, conn_id: str) -> Connection:
  62. """
  63. Get connection, given connection id.
  64. :param conn_id: connection id
  65. :return: connection
  66. """
  67. from airflow.models.connection import Connection
  68. conn = Connection.get_connection_from_secrets(conn_id)
  69. log.info("Retrieving connection '%s'", conn.conn_id)
  70. return conn
  71. @classmethod
  72. def get_hook(cls, conn_id: str) -> BaseHook:
  73. """
  74. Return default hook for this connection id.
  75. :param conn_id: connection id
  76. :return: default hook for this connection
  77. """
  78. connection = cls.get_connection(conn_id)
  79. return connection.get_hook()
  80. def get_conn(self) -> Any:
  81. """Return connection for the hook."""
  82. raise NotImplementedError()
  83. @classmethod
  84. def get_connection_form_widgets(cls) -> dict[str, Any]:
  85. return {}
  86. @classmethod
  87. def get_ui_field_behaviour(cls) -> dict[str, Any]:
  88. return {}
  89. class DiscoverableHook(Protocol):
  90. """
  91. Interface that providers *can* implement to be discovered by ProvidersManager.
  92. It is not used by any of the Hooks, but simply methods and class fields described here are
  93. implemented by those Hooks. Each method is optional -- only implement the ones you need.
  94. The conn_name_attr, default_conn_name, conn_type should be implemented by those
  95. Hooks that want to be automatically mapped from the connection_type -> Hook when get_hook method
  96. is called with connection_type.
  97. Additionally hook_name should be set when you want the hook to have a custom name in the UI selection
  98. Name. If not specified, conn_name will be used.
  99. The "get_ui_field_behaviour" and "get_connection_form_widgets" are optional - override them if you want
  100. to customize the Connection Form screen. You can add extra widgets to parse your extra fields via the
  101. get_connection_form_widgets method as well as hide or relabel the fields or pre-fill
  102. them with placeholders via get_ui_field_behaviour method.
  103. Note that the "get_ui_field_behaviour" and "get_connection_form_widgets" need to be set by each class
  104. in the class hierarchy in order to apply widget customizations.
  105. For example, even if you want to use the fields from your parent class, you must explicitly
  106. have a method on *your* class:
  107. .. code-block:: python
  108. @classmethod
  109. def get_ui_field_behaviour(cls):
  110. return super().get_ui_field_behaviour()
  111. You also need to add the Hook class name to list 'hook_class_names' in provider.yaml in case you
  112. build an internal provider or to return it in dictionary returned by provider_info entrypoint in the
  113. package you prepare.
  114. You can see some examples in airflow/providers/jdbc/hooks/jdbc.py.
  115. """
  116. conn_name_attr: str
  117. default_conn_name: str
  118. conn_type: str
  119. hook_name: str
  120. @staticmethod
  121. def get_connection_form_widgets() -> dict[str, Any]:
  122. """
  123. Return dictionary of widgets to be added for the hook to handle extra values.
  124. If you have class hierarchy, usually the widgets needed by your class are already
  125. added by the base class, so there is no need to implement this method. It might
  126. actually result in warning in the logs if you try to add widgets that have already
  127. been added by the base class.
  128. Note that values of Dict should be of wtforms.Field type. It's not added here
  129. for the efficiency of imports.
  130. """
  131. ...
  132. @staticmethod
  133. def get_ui_field_behaviour() -> dict[str, Any]:
  134. """
  135. Attributes of the UI field.
  136. Returns dictionary describing customizations to implement in javascript handling the
  137. connection form. Should be compliant with airflow/customized_form_field_behaviours.schema.json'
  138. If you change conn_type in a derived class, you should also
  139. implement this method and return field customizations appropriate to your Hook. This
  140. is because the child hook will have usually different conn_type and the customizations
  141. are per connection type.
  142. .. seealso::
  143. :class:`~airflow.providers.google.cloud.hooks.compute_ssh.ComputeSSH` as an example
  144. """
  145. ...