apache-airflow-providers-standard 1.9.0__py3-none-any.whl → 1.9.2rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. airflow/providers/standard/__init__.py +1 -1
  2. airflow/providers/standard/decorators/bash.py +7 -13
  3. airflow/providers/standard/decorators/branch_external_python.py +2 -8
  4. airflow/providers/standard/decorators/branch_python.py +2 -7
  5. airflow/providers/standard/decorators/branch_virtualenv.py +2 -7
  6. airflow/providers/standard/decorators/external_python.py +2 -7
  7. airflow/providers/standard/decorators/python.py +2 -7
  8. airflow/providers/standard/decorators/python_virtualenv.py +2 -9
  9. airflow/providers/standard/decorators/sensor.py +2 -9
  10. airflow/providers/standard/decorators/short_circuit.py +2 -8
  11. airflow/providers/standard/decorators/stub.py +6 -12
  12. airflow/providers/standard/example_dags/example_bash_decorator.py +1 -6
  13. airflow/providers/standard/example_dags/example_branch_operator.py +1 -6
  14. airflow/providers/standard/example_dags/example_branch_operator_decorator.py +1 -6
  15. airflow/providers/standard/example_dags/example_external_task_parent_deferrable.py +2 -6
  16. airflow/providers/standard/example_dags/example_hitl_operator.py +1 -1
  17. airflow/providers/standard/example_dags/example_sensors.py +1 -6
  18. airflow/providers/standard/example_dags/example_short_circuit_decorator.py +1 -6
  19. airflow/providers/standard/example_dags/example_short_circuit_operator.py +1 -6
  20. airflow/providers/standard/hooks/filesystem.py +1 -1
  21. airflow/providers/standard/hooks/package_index.py +1 -1
  22. airflow/providers/standard/hooks/subprocess.py +3 -10
  23. airflow/providers/standard/operators/bash.py +3 -12
  24. airflow/providers/standard/operators/branch.py +1 -1
  25. airflow/providers/standard/operators/datetime.py +2 -6
  26. airflow/providers/standard/operators/empty.py +1 -1
  27. airflow/providers/standard/operators/hitl.py +12 -9
  28. airflow/providers/standard/operators/latest_only.py +3 -8
  29. airflow/providers/standard/operators/python.py +9 -9
  30. airflow/providers/standard/operators/smooth.py +1 -1
  31. airflow/providers/standard/operators/trigger_dagrun.py +16 -21
  32. airflow/providers/standard/operators/weekday.py +2 -6
  33. airflow/providers/standard/sensors/bash.py +3 -8
  34. airflow/providers/standard/sensors/date_time.py +2 -6
  35. airflow/providers/standard/sensors/external_task.py +77 -55
  36. airflow/providers/standard/sensors/filesystem.py +1 -1
  37. airflow/providers/standard/sensors/python.py +2 -6
  38. airflow/providers/standard/sensors/time.py +1 -6
  39. airflow/providers/standard/sensors/time_delta.py +3 -7
  40. airflow/providers/standard/sensors/weekday.py +2 -7
  41. airflow/providers/standard/triggers/external_task.py +36 -36
  42. airflow/providers/standard/triggers/file.py +1 -1
  43. airflow/providers/standard/triggers/hitl.py +135 -86
  44. airflow/providers/standard/triggers/temporal.py +1 -5
  45. airflow/providers/standard/utils/python_virtualenv.py +36 -3
  46. airflow/providers/standard/utils/sensor_helper.py +19 -8
  47. airflow/providers/standard/utils/skipmixin.py +1 -7
  48. airflow/providers/standard/version_compat.py +4 -21
  49. {apache_airflow_providers_standard-1.9.0.dist-info → apache_airflow_providers_standard-1.9.2rc1.dist-info}/METADATA +36 -13
  50. apache_airflow_providers_standard-1.9.2rc1.dist-info/RECORD +78 -0
  51. apache_airflow_providers_standard-1.9.2rc1.dist-info/licenses/NOTICE +5 -0
  52. apache_airflow_providers_standard-1.9.0.dist-info/RECORD +0 -77
  53. {apache_airflow_providers_standard-1.9.0.dist-info → apache_airflow_providers_standard-1.9.2rc1.dist-info}/WHEEL +0 -0
  54. {apache_airflow_providers_standard-1.9.0.dist-info → apache_airflow_providers_standard-1.9.2rc1.dist-info}/entry_points.txt +0 -0
  55. {airflow/providers/standard → apache_airflow_providers_standard-1.9.2rc1.dist-info/licenses}/LICENSE +0 -0
@@ -30,6 +30,9 @@ from uuid import UUID
30
30
 
31
31
  from asgiref.sync import sync_to_async
32
32
 
33
+ from airflow.exceptions import ParamValidationError
34
+ from airflow.sdk import Param
35
+ from airflow.sdk.definitions.param import ParamsDict
33
36
  from airflow.sdk.execution_time.hitl import (
34
37
  HITLUser,
35
38
  get_hitl_detail_content_detail,
@@ -43,7 +46,7 @@ class HITLTriggerEventSuccessPayload(TypedDict, total=False):
43
46
  """Minimum required keys for a success Human-in-the-loop TriggerEvent."""
44
47
 
45
48
  chosen_options: list[str]
46
- params_input: dict[str, Any]
49
+ params_input: dict[str, dict[str, Any]]
47
50
  responded_by_user: HITLUser | None
48
51
  responded_at: datetime
49
52
  timedout: bool
@@ -53,7 +56,7 @@ class HITLTriggerEventFailurePayload(TypedDict):
53
56
  """Minimum required keys for a failed Human-in-the-loop TriggerEvent."""
54
57
 
55
58
  error: str
56
- error_type: Literal["timeout", "unknown"]
59
+ error_type: Literal["timeout", "unknown", "validation"]
57
60
 
58
61
 
59
62
  class HITLTrigger(BaseTrigger):
@@ -64,7 +67,7 @@ class HITLTrigger(BaseTrigger):
64
67
  *,
65
68
  ti_id: UUID,
66
69
  options: list[str],
67
- params: dict[str, Any],
70
+ params: dict[str, dict[str, Any]],
68
71
  defaults: list[str] | None = None,
69
72
  multiple: bool = False,
70
73
  timeout_datetime: datetime | None,
@@ -80,7 +83,21 @@ class HITLTrigger(BaseTrigger):
80
83
  self.defaults = defaults
81
84
  self.timeout_datetime = timeout_datetime
82
85
 
83
- self.params = params
86
+ self.params = ParamsDict(
87
+ {
88
+ k: Param(
89
+ v.pop("value"),
90
+ **v,
91
+ )
92
+ if HITLTrigger._is_param(v)
93
+ else Param(v)
94
+ for k, v in params.items()
95
+ },
96
+ )
97
+
98
+ @staticmethod
99
+ def _is_param(value: Any) -> bool:
100
+ return isinstance(value, dict) and all(key in value for key in ("description", "schema", "value"))
84
101
 
85
102
  def serialize(self) -> tuple[str, dict[str, Any]]:
86
103
  """Serialize HITLTrigger arguments and classpath."""
@@ -90,99 +107,131 @@ class HITLTrigger(BaseTrigger):
90
107
  "ti_id": self.ti_id,
91
108
  "options": self.options,
92
109
  "defaults": self.defaults,
93
- "params": self.params,
110
+ "params": {k: self.params.get_param(k).serialize() for k in self.params},
94
111
  "multiple": self.multiple,
95
112
  "timeout_datetime": self.timeout_datetime,
96
113
  "poke_interval": self.poke_interval,
97
114
  },
98
115
  )
99
116
 
100
- async def run(self) -> AsyncIterator[TriggerEvent]:
101
- """Loop until the Human-in-the-loop response received or timeout reached."""
102
- while True:
103
- if self.timeout_datetime and self.timeout_datetime < utcnow():
104
- # Fetch latest HITL detail before fallback
105
- resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
106
- # Response already received, yield success and exit
107
- if resp.response_received and resp.chosen_options:
108
- if TYPE_CHECKING:
109
- assert resp.responded_by_user is not None
110
- assert resp.responded_at is not None
111
-
112
- self.log.info(
113
- "[HITL] responded_by=%s (id=%s) options=%s at %s (timeout fallback skipped)",
114
- resp.responded_by_user.name,
115
- resp.responded_by_user.id,
116
- resp.chosen_options,
117
- resp.responded_at,
118
- )
119
- yield TriggerEvent(
120
- HITLTriggerEventSuccessPayload(
121
- chosen_options=resp.chosen_options,
122
- params_input=resp.params_input or {},
123
- responded_at=resp.responded_at,
124
- responded_by_user=HITLUser(
125
- id=resp.responded_by_user.id,
126
- name=resp.responded_by_user.name,
127
- ),
128
- timedout=False,
129
- )
130
- )
131
- return
132
-
133
- if self.defaults is None:
134
- yield TriggerEvent(
135
- HITLTriggerEventFailurePayload(
136
- error="The timeout has passed, and the response has not yet been received.",
137
- error_type="timeout",
138
- )
139
- )
140
- return
141
-
142
- resp = await sync_to_async(update_hitl_detail_response)(
143
- ti_id=self.ti_id,
144
- chosen_options=self.defaults,
145
- params_input=self.params,
117
+ async def _handle_timeout(self) -> TriggerEvent:
118
+ """Handle HITL timeout logic and yield appropriate event."""
119
+ resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
120
+
121
+ # Case 1: Response arrived just before timeout
122
+ if resp.response_received and resp.chosen_options:
123
+ if TYPE_CHECKING:
124
+ assert resp.responded_by_user is not None
125
+ assert resp.responded_at is not None
126
+
127
+ chosen_options_list = list(resp.chosen_options or [])
128
+ self.log.info(
129
+ "[HITL] responded_by=%s (id=%s) options=%s at %s (timeout fallback skipped)",
130
+ resp.responded_by_user.name,
131
+ resp.responded_by_user.id,
132
+ chosen_options_list,
133
+ resp.responded_at,
134
+ )
135
+ return TriggerEvent(
136
+ HITLTriggerEventSuccessPayload(
137
+ chosen_options=chosen_options_list,
138
+ params_input=resp.params_input or {},
139
+ responded_at=resp.responded_at,
140
+ responded_by_user=HITLUser(
141
+ id=resp.responded_by_user.id,
142
+ name=resp.responded_by_user.name,
143
+ ),
144
+ timedout=False,
146
145
  )
147
- if TYPE_CHECKING:
148
- assert resp.responded_at is not None
149
- self.log.info(
150
- "[HITL] timeout reached before receiving response, fallback to default %s", self.defaults
146
+ )
147
+
148
+ # Case 2: No defaults defined → failure
149
+ if self.defaults is None:
150
+ return TriggerEvent(
151
+ HITLTriggerEventFailurePayload(
152
+ error="The timeout has passed, and the response has not yet been received.",
153
+ error_type="timeout",
151
154
  )
152
- yield TriggerEvent(
153
- HITLTriggerEventSuccessPayload(
154
- chosen_options=self.defaults,
155
- params_input=self.params,
156
- responded_by_user=None,
157
- responded_at=resp.responded_at,
158
- timedout=True,
155
+ )
156
+
157
+ # Case 3: Timeout fallback to default
158
+ resp = await sync_to_async(update_hitl_detail_response)(
159
+ ti_id=self.ti_id,
160
+ chosen_options=self.defaults,
161
+ params_input=self.params.dump(),
162
+ )
163
+ if TYPE_CHECKING:
164
+ assert resp.responded_at is not None
165
+
166
+ self.log.info(
167
+ "[HITL] timeout reached before receiving response, fallback to default %s",
168
+ self.defaults,
169
+ )
170
+ return TriggerEvent(
171
+ HITLTriggerEventSuccessPayload(
172
+ chosen_options=self.defaults,
173
+ params_input=self.params.dump(),
174
+ responded_by_user=None,
175
+ responded_at=resp.responded_at,
176
+ timedout=True,
177
+ )
178
+ )
179
+
180
+ async def _handle_response(self):
181
+ """Check if HITL response is ready and yield success if so."""
182
+ resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
183
+ if TYPE_CHECKING:
184
+ assert resp.responded_by_user is not None
185
+ assert resp.responded_at is not None
186
+
187
+ if not (resp.response_received and resp.chosen_options):
188
+ return None
189
+
190
+ # validate input
191
+ if params_input := resp.params_input:
192
+ try:
193
+ for key, value in params_input.items():
194
+ self.params[key] = value
195
+ except ParamValidationError as err:
196
+ return TriggerEvent(
197
+ HITLTriggerEventFailurePayload(
198
+ error=str(err),
199
+ error_type="validation",
159
200
  )
160
201
  )
202
+
203
+ chosen_options_list = list(resp.chosen_options or [])
204
+ self.log.info(
205
+ "[HITL] responded_by=%s (id=%s) options=%s at %s",
206
+ resp.responded_by_user.name,
207
+ resp.responded_by_user.id,
208
+ chosen_options_list,
209
+ resp.responded_at,
210
+ )
211
+ return TriggerEvent(
212
+ HITLTriggerEventSuccessPayload(
213
+ chosen_options=chosen_options_list,
214
+ params_input=params_input or {},
215
+ responded_at=resp.responded_at,
216
+ responded_by_user=HITLUser(
217
+ id=resp.responded_by_user.id,
218
+ name=resp.responded_by_user.name,
219
+ ),
220
+ timedout=False,
221
+ )
222
+ )
223
+
224
+ async def run(self) -> AsyncIterator[TriggerEvent]:
225
+ """Loop until the Human-in-the-loop response received or timeout reached."""
226
+ while True:
227
+ if self.timeout_datetime and self.timeout_datetime < utcnow():
228
+ event = await self._handle_timeout()
229
+ yield event
161
230
  return
162
231
 
163
- resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
164
- if resp.response_received and resp.chosen_options:
165
- if TYPE_CHECKING:
166
- assert resp.responded_by_user is not None
167
- assert resp.responded_at is not None
168
- self.log.info(
169
- "[HITL] responded_by=%s (id=%s) options=%s at %s",
170
- resp.responded_by_user.name,
171
- resp.responded_by_user.id,
172
- resp.chosen_options,
173
- resp.responded_at,
174
- )
175
- yield TriggerEvent(
176
- HITLTriggerEventSuccessPayload(
177
- chosen_options=resp.chosen_options,
178
- params_input=resp.params_input or {},
179
- responded_at=resp.responded_at,
180
- responded_by_user=HITLUser(
181
- id=resp.responded_by_user.id,
182
- name=resp.responded_by_user.name,
183
- ),
184
- timedout=False,
185
- )
186
- )
232
+ event = await self._handle_response()
233
+ if event:
234
+ yield event
187
235
  return
236
+
188
237
  await asyncio.sleep(self.poke_interval)
@@ -23,13 +23,9 @@ from typing import Any
23
23
 
24
24
  import pendulum
25
25
 
26
+ from airflow.providers.common.compat.sdk import timezone
26
27
  from airflow.triggers.base import BaseTrigger, TaskSuccessEvent, TriggerEvent
27
28
 
28
- try:
29
- from airflow.sdk import timezone
30
- except ImportError:
31
- from airflow.utils import timezone # type: ignore[attr-defined,no-redef]
32
-
33
29
 
34
30
  class DateTimeTrigger(BaseTrigger):
35
31
  """
@@ -19,8 +19,11 @@
19
19
 
20
20
  from __future__ import annotations
21
21
 
22
+ import logging
22
23
  import os
24
+ import shlex
23
25
  import shutil
26
+ import subprocess
24
27
  import warnings
25
28
  from pathlib import Path
26
29
 
@@ -28,7 +31,6 @@ import jinja2
28
31
  from jinja2 import select_autoescape
29
32
 
30
33
  from airflow.configuration import conf
31
- from airflow.utils.process_utils import execute_in_subprocess
32
34
 
33
35
 
34
36
  def _is_uv_installed() -> bool:
@@ -132,6 +134,37 @@ def _index_urls_to_uv_env_vars(index_urls: list[str] | None = None) -> dict[str,
132
134
  return uv_index_env_vars
133
135
 
134
136
 
137
+ def _execute_in_subprocess(cmd: list[str], cwd: str | None = None, env: dict[str, str] | None = None) -> None:
138
+ """
139
+ Execute a process and stream output to logger.
140
+
141
+ :param cmd: command and arguments to run
142
+ :param cwd: Current working directory passed to the Popen constructor
143
+ :param env: Additional environment variables to set for the subprocess.
144
+ """
145
+ log = logging.getLogger(__name__)
146
+
147
+ log.info("Executing cmd: %s", " ".join(shlex.quote(c) for c in cmd))
148
+ with subprocess.Popen(
149
+ cmd,
150
+ stdout=subprocess.PIPE,
151
+ stderr=subprocess.STDOUT,
152
+ bufsize=0,
153
+ close_fds=True,
154
+ cwd=cwd,
155
+ env=env,
156
+ ) as proc:
157
+ log.info("Output:")
158
+ if proc.stdout:
159
+ with proc.stdout:
160
+ for line in iter(proc.stdout.readline, b""):
161
+ log.info("%s", line.decode().rstrip())
162
+
163
+ exit_code = proc.wait()
164
+ if exit_code != 0:
165
+ raise subprocess.CalledProcessError(exit_code, cmd)
166
+
167
+
135
168
  def prepare_virtualenv(
136
169
  venv_directory: str,
137
170
  python_bin: str,
@@ -169,7 +202,7 @@ def prepare_virtualenv(
169
202
  venv_cmd = _generate_uv_cmd(venv_directory, python_bin, system_site_packages)
170
203
  else:
171
204
  venv_cmd = _generate_venv_cmd(venv_directory, python_bin, system_site_packages)
172
- execute_in_subprocess(venv_cmd)
205
+ _execute_in_subprocess(venv_cmd)
173
206
 
174
207
  pip_cmd = None
175
208
  if requirements is not None and len(requirements) != 0:
@@ -188,7 +221,7 @@ def prepare_virtualenv(
188
221
  )
189
222
 
190
223
  if pip_cmd:
191
- execute_in_subprocess(pip_cmd, env={**os.environ, **_index_urls_to_uv_env_vars(index_urls)})
224
+ _execute_in_subprocess(pip_cmd, env={**os.environ, **_index_urls_to_uv_env_vars(index_urls)})
192
225
 
193
226
  return f"{venv_directory}/bin/python"
194
227
 
@@ -16,6 +16,7 @@
16
16
  # under the License.
17
17
  from __future__ import annotations
18
18
 
19
+ from collections.abc import Collection
19
20
  from typing import TYPE_CHECKING, Any, cast
20
21
 
21
22
  from sqlalchemy import func, select, tuple_
@@ -27,7 +28,7 @@ from airflow.utils.session import NEW_SESSION, provide_session
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  from sqlalchemy.orm import Session
30
- from sqlalchemy.sql import Executable
31
+ from sqlalchemy.sql import Select
31
32
 
32
33
 
33
34
  @provide_session
@@ -59,6 +60,7 @@ def _get_count(
59
60
  session.scalar(
60
61
  _count_stmt(TI, states, dttm_filter, external_dag_id).where(TI.task_id.in_(external_task_ids))
61
62
  )
63
+ or 0
62
64
  ) / len(external_task_ids)
63
65
  elif external_task_group_id:
64
66
  external_task_group_task_ids = _get_external_task_group_task_ids(
@@ -68,20 +70,25 @@ def _get_count(
68
70
  count = 0
69
71
  else:
70
72
  count = (
71
- session.scalar(
72
- _count_stmt(TI, states, dttm_filter, external_dag_id).where(
73
- tuple_(TI.task_id, TI.map_index).in_(external_task_group_task_ids)
73
+ (
74
+ session.scalar(
75
+ _count_stmt(TI, states, dttm_filter, external_dag_id).where(
76
+ tuple_(TI.task_id, TI.map_index).in_(external_task_group_task_ids)
77
+ )
74
78
  )
79
+ or 0
75
80
  )
76
81
  / len(external_task_group_task_ids)
77
82
  * len(dttm_filter)
78
83
  )
79
84
  else:
80
- count = session.scalar(_count_stmt(DR, states, dttm_filter, external_dag_id))
85
+ count = session.scalar(_count_stmt(DR, states, dttm_filter, external_dag_id)) or 0
81
86
  return cast("int", count)
82
87
 
83
88
 
84
- def _count_stmt(model, states, dttm_filter, external_dag_id) -> Executable:
89
+ def _count_stmt(
90
+ model: type[DagRun] | type[TaskInstance], states: list[str], dttm_filter: list[Any], external_dag_id: str
91
+ ) -> Select[tuple[int]]:
85
92
  """
86
93
  Get the count of records against dttm filter and states.
87
94
 
@@ -97,7 +104,9 @@ def _count_stmt(model, states, dttm_filter, external_dag_id) -> Executable:
97
104
  )
98
105
 
99
106
 
100
- def _get_external_task_group_task_ids(dttm_filter, external_task_group_id, external_dag_id, session):
107
+ def _get_external_task_group_task_ids(
108
+ dttm_filter: list[Any], external_task_group_id: str, external_dag_id: str, session: Session
109
+ ) -> list[tuple[str, int]]:
101
110
  """
102
111
  Get the count of records against dttm filter and states.
103
112
 
@@ -107,6 +116,8 @@ def _get_external_task_group_task_ids(dttm_filter, external_task_group_id, exter
107
116
  :param session: airflow session object
108
117
  """
109
118
  refreshed_dag_info = SerializedDagModel.get_dag(external_dag_id, session=session)
119
+ if not refreshed_dag_info:
120
+ return [(external_task_group_id, -1)]
110
121
  task_group = refreshed_dag_info.task_group_dict.get(external_task_group_id)
111
122
 
112
123
  if task_group:
@@ -129,7 +140,7 @@ def _get_external_task_group_task_ids(dttm_filter, external_task_group_id, exter
129
140
 
130
141
  def _get_count_by_matched_states(
131
142
  run_id_task_state_map: dict[str, dict[str, Any]],
132
- states: list[str],
143
+ states: Collection[str],
133
144
  ):
134
145
  count = 0
135
146
  for _, task_states in run_id_task_state_map.items():
@@ -22,7 +22,6 @@ from types import GeneratorType
22
22
  from typing import TYPE_CHECKING
23
23
 
24
24
  from airflow.exceptions import AirflowException
25
- from airflow.providers.standard.version_compat import AIRFLOW_V_3_0_PLUS
26
25
  from airflow.utils.log.logging_mixin import LoggingMixin
27
26
 
28
27
  if TYPE_CHECKING:
@@ -40,12 +39,7 @@ XCOM_SKIPMIXIN_FOLLOWED = "followed"
40
39
 
41
40
 
42
41
  def _ensure_tasks(nodes: Iterable[DAGNode]) -> Sequence[Operator]:
43
- if AIRFLOW_V_3_0_PLUS:
44
- from airflow.sdk import BaseOperator
45
- from airflow.sdk.definitions.mappedoperator import MappedOperator
46
- else:
47
- from airflow.models.baseoperator import BaseOperator # type: ignore[no-redef]
48
- from airflow.models.mappedoperator import MappedOperator # type: ignore[assignment,no-redef]
42
+ from airflow.providers.common.compat.sdk import BaseOperator, MappedOperator
49
43
 
50
44
  return [n for n in nodes if isinstance(n, (BaseOperator, MappedOperator))]
51
45
 
@@ -34,36 +34,19 @@ def get_base_airflow_version_tuple() -> tuple[int, int, int]:
34
34
 
35
35
  AIRFLOW_V_3_0_PLUS: bool = get_base_airflow_version_tuple() >= (3, 0, 0)
36
36
  AIRFLOW_V_3_1_PLUS: bool = get_base_airflow_version_tuple() >= (3, 1, 0)
37
+ AIRFLOW_V_3_1_3_PLUS: bool = get_base_airflow_version_tuple() >= (3, 1, 3)
37
38
  AIRFLOW_V_3_2_PLUS: bool = get_base_airflow_version_tuple() >= (3, 2, 0)
38
39
 
39
- # BaseOperator is not imported from SDK from 3.0 (and only done from 3.1) due to a bug with
40
- # DecoratedOperator -- where `DecoratedOperator._handle_output` needed `xcom_push` to exist on `BaseOperator`
41
- # even though it wasn't used.
40
+ # BaseOperator: Use 3.1+ due to xcom_push method missing in SDK BaseOperator 3.0.x
41
+ # This is needed for DecoratedOperator compatibility
42
42
  if AIRFLOW_V_3_1_PLUS:
43
- from airflow.sdk import BaseHook, BaseOperator, timezone
44
- from airflow.sdk.definitions.context import context_merge
43
+ from airflow.sdk import BaseOperator
45
44
  else:
46
- from airflow.hooks.base import BaseHook # type: ignore[attr-defined,no-redef]
47
45
  from airflow.models.baseoperator import BaseOperator # type: ignore[no-redef]
48
- from airflow.utils import timezone # type: ignore[attr-defined,no-redef]
49
- from airflow.utils.context import context_merge # type: ignore[no-redef, attr-defined]
50
-
51
- if AIRFLOW_V_3_0_PLUS:
52
- from airflow.sdk import BaseOperatorLink
53
- from airflow.sdk.bases.sensor import BaseSensorOperator, PokeReturnValue
54
- else:
55
- from airflow.models.baseoperatorlink import BaseOperatorLink # type: ignore[no-redef]
56
- from airflow.sensors.base import BaseSensorOperator, PokeReturnValue # type: ignore[no-redef]
57
46
 
58
47
  __all__ = [
59
48
  "AIRFLOW_V_3_0_PLUS",
60
49
  "AIRFLOW_V_3_1_PLUS",
61
50
  "AIRFLOW_V_3_2_PLUS",
62
51
  "BaseOperator",
63
- "BaseOperatorLink",
64
- "BaseHook",
65
- "BaseSensorOperator",
66
- "PokeReturnValue",
67
- "context_merge",
68
- "timezone",
69
52
  ]
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apache-airflow-providers-standard
3
- Version: 1.9.0
3
+ Version: 1.9.2rc1
4
4
  Summary: Provider package apache-airflow-providers-standard for Apache Airflow
5
5
  Keywords: airflow-provider,standard,airflow,integration
6
6
  Author-email: Apache Software Foundation <dev@airflow.apache.org>
7
7
  Maintainer-email: Apache Software Foundation <dev@airflow.apache.org>
8
8
  Requires-Python: >=3.10
9
9
  Description-Content-Type: text/x-rst
10
+ License-Expression: Apache-2.0
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Environment :: Console
12
13
  Classifier: Environment :: Web Environment
@@ -14,16 +15,18 @@ Classifier: Intended Audience :: Developers
14
15
  Classifier: Intended Audience :: System Administrators
15
16
  Classifier: Framework :: Apache Airflow
16
17
  Classifier: Framework :: Apache Airflow :: Provider
17
- Classifier: License :: OSI Approved :: Apache Software License
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: System :: Monitoring
23
- Requires-Dist: apache-airflow>=2.10.0
23
+ License-File: LICENSE
24
+ License-File: NOTICE
25
+ Requires-Dist: apache-airflow>=2.10.0rc1
26
+ Requires-Dist: apache-airflow-providers-common-compat>=1.8.0rc1
24
27
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
25
- Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.0/changelog.html
26
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.0
28
+ Project-URL: Changelog, https://airflow.staged.apache.org/docs/apache-airflow-providers-standard/1.9.2/changelog.html
29
+ Project-URL: Documentation, https://airflow.staged.apache.org/docs/apache-airflow-providers-standard/1.9.2
27
30
  Project-URL: Mastodon, https://fosstodon.org/@airflow
28
31
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
29
32
  Project-URL: Source Code, https://github.com/apache/airflow
@@ -54,7 +57,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
54
57
 
55
58
  Package ``apache-airflow-providers-standard``
56
59
 
57
- Release: ``1.9.0``
60
+ Release: ``1.9.2``
58
61
 
59
62
 
60
63
  Airflow Standard Provider
@@ -67,7 +70,7 @@ This is a provider package for ``standard`` provider. All classes for this provi
67
70
  are in ``airflow.providers.standard`` python package.
68
71
 
69
72
  You can find package information and changelog for the provider
70
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.0/>`_.
73
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.2/>`_.
71
74
 
72
75
  Installation
73
76
  ------------
@@ -81,12 +84,32 @@ The package supports the following python versions: 3.10,3.11,3.12,3.13
81
84
  Requirements
82
85
  ------------
83
86
 
84
- ================== ==================
85
- PIP package Version required
86
- ================== ==================
87
- ``apache-airflow`` ``>=2.10.0``
88
- ================== ==================
87
+ ========================================== ==================
88
+ PIP package Version required
89
+ ========================================== ==================
90
+ ``apache-airflow`` ``>=2.10.0``
91
+ ``apache-airflow-providers-common-compat`` ``>=1.8.0``
92
+ ========================================== ==================
93
+
94
+ Cross provider package dependencies
95
+ -----------------------------------
96
+
97
+ Those are dependencies that might be needed in order to use all the features of the package.
98
+ You need to install the specified providers in order to use them.
99
+
100
+ You can install such cross-provider dependencies when installing from PyPI. For example:
101
+
102
+ .. code-block:: bash
103
+
104
+ pip install apache-airflow-providers-standard[common.compat]
105
+
106
+
107
+ ================================================================================================================== =================
108
+ Dependent package Extra
109
+ ================================================================================================================== =================
110
+ `apache-airflow-providers-common-compat <https://airflow.apache.org/docs/apache-airflow-providers-common-compat>`_ ``common.compat``
111
+ ================================================================================================================== =================
89
112
 
90
113
  The changelog for the provider package can be found in the
91
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.0/changelog.html>`_.
114
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-standard/1.9.2/changelog.html>`_.
92
115