baseoperator.py 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144
  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. """
  19. Base operator for all operators.
  20. :sphinx-autoapi-skip:
  21. """
  22. from __future__ import annotations
  23. import abc
  24. import collections.abc
  25. import contextlib
  26. import copy
  27. import functools
  28. import inspect
  29. import logging
  30. import sys
  31. import warnings
  32. from datetime import datetime, timedelta
  33. from functools import total_ordering, wraps
  34. from threading import local
  35. from types import FunctionType
  36. from typing import (
  37. TYPE_CHECKING,
  38. Any,
  39. Callable,
  40. Collection,
  41. Iterable,
  42. NoReturn,
  43. Sequence,
  44. TypeVar,
  45. Union,
  46. cast,
  47. )
  48. import attr
  49. import pendulum
  50. from dateutil.relativedelta import relativedelta
  51. from sqlalchemy import select
  52. from sqlalchemy.orm.exc import NoResultFound
  53. from airflow.configuration import conf
  54. from airflow.exceptions import (
  55. AirflowException,
  56. FailStopDagInvalidTriggerRule,
  57. RemovedInAirflow3Warning,
  58. TaskDeferralError,
  59. TaskDeferred,
  60. )
  61. from airflow.lineage import apply_lineage, prepare_lineage
  62. from airflow.models.abstractoperator import (
  63. DEFAULT_EXECUTOR,
  64. DEFAULT_IGNORE_FIRST_DEPENDS_ON_PAST,
  65. DEFAULT_OWNER,
  66. DEFAULT_POOL_SLOTS,
  67. DEFAULT_PRIORITY_WEIGHT,
  68. DEFAULT_QUEUE,
  69. DEFAULT_RETRIES,
  70. DEFAULT_RETRY_DELAY,
  71. DEFAULT_TASK_EXECUTION_TIMEOUT,
  72. DEFAULT_TRIGGER_RULE,
  73. DEFAULT_WAIT_FOR_PAST_DEPENDS_BEFORE_SKIPPING,
  74. DEFAULT_WEIGHT_RULE,
  75. AbstractOperator,
  76. )
  77. from airflow.models.base import _sentinel
  78. from airflow.models.mappedoperator import OperatorPartial, validate_mapping_kwargs
  79. from airflow.models.param import ParamsDict
  80. from airflow.models.pool import Pool
  81. from airflow.models.taskinstance import TaskInstance, clear_task_instances
  82. from airflow.models.taskmixin import DependencyMixin
  83. from airflow.serialization.enums import DagAttributeTypes
  84. from airflow.task.priority_strategy import PriorityWeightStrategy, validate_and_load_priority_weight_strategy
  85. from airflow.ti_deps.deps.mapped_task_upstream_dep import MappedTaskUpstreamDep
  86. from airflow.ti_deps.deps.not_in_retry_period_dep import NotInRetryPeriodDep
  87. from airflow.ti_deps.deps.not_previously_skipped_dep import NotPreviouslySkippedDep
  88. from airflow.ti_deps.deps.prev_dagrun_dep import PrevDagrunDep
  89. from airflow.ti_deps.deps.trigger_rule_dep import TriggerRuleDep
  90. from airflow.utils import timezone
  91. from airflow.utils.context import Context, context_get_outlet_events
  92. from airflow.utils.decorators import fixup_decorator_warning_stack
  93. from airflow.utils.edgemodifier import EdgeModifier
  94. from airflow.utils.helpers import validate_instance_args, validate_key
  95. from airflow.utils.operator_helpers import ExecutionCallableRunner
  96. from airflow.utils.operator_resources import Resources
  97. from airflow.utils.session import NEW_SESSION, provide_session
  98. from airflow.utils.setup_teardown import SetupTeardownContext
  99. from airflow.utils.trigger_rule import TriggerRule
  100. from airflow.utils.types import NOTSET, AttributeRemoved
  101. from airflow.utils.xcom import XCOM_RETURN_KEY
  102. if TYPE_CHECKING:
  103. from types import ClassMethodDescriptorType
  104. import jinja2 # Slow import.
  105. from sqlalchemy.orm import Session
  106. from airflow.models.abstractoperator import TaskStateChangeCallback
  107. from airflow.models.baseoperatorlink import BaseOperatorLink
  108. from airflow.models.dag import DAG
  109. from airflow.models.operator import Operator
  110. from airflow.models.xcom_arg import XComArg
  111. from airflow.ti_deps.deps.base_ti_dep import BaseTIDep
  112. from airflow.triggers.base import BaseTrigger, StartTriggerArgs
  113. from airflow.utils.task_group import TaskGroup
  114. from airflow.utils.types import ArgNotSet
  115. ScheduleInterval = Union[str, timedelta, relativedelta]
  116. TaskPreExecuteHook = Callable[[Context], None]
  117. TaskPostExecuteHook = Callable[[Context, Any], None]
  118. T = TypeVar("T", bound=FunctionType)
  119. logger = logging.getLogger("airflow.models.baseoperator.BaseOperator")
  120. def parse_retries(retries: Any) -> int | None:
  121. if retries is None:
  122. return 0
  123. elif type(retries) == int: # noqa: E721
  124. return retries
  125. try:
  126. parsed_retries = int(retries)
  127. except (TypeError, ValueError):
  128. raise AirflowException(f"'retries' type must be int, not {type(retries).__name__}")
  129. logger.warning("Implicitly converting 'retries' from %r to int", retries)
  130. return parsed_retries
  131. def coerce_timedelta(value: float | timedelta, *, key: str) -> timedelta:
  132. if isinstance(value, timedelta):
  133. return value
  134. logger.debug("%s isn't a timedelta object, assuming secs", key)
  135. return timedelta(seconds=value)
  136. def coerce_resources(resources: dict[str, Any] | None) -> Resources | None:
  137. if resources is None:
  138. return None
  139. return Resources(**resources)
  140. def _get_parent_defaults(dag: DAG | None, task_group: TaskGroup | None) -> tuple[dict, ParamsDict]:
  141. if not dag:
  142. return {}, ParamsDict()
  143. dag_args = copy.copy(dag.default_args)
  144. dag_params = copy.deepcopy(dag.params)
  145. if task_group:
  146. if task_group.default_args and not isinstance(task_group.default_args, collections.abc.Mapping):
  147. raise TypeError("default_args must be a mapping")
  148. dag_args.update(task_group.default_args)
  149. return dag_args, dag_params
  150. def get_merged_defaults(
  151. dag: DAG | None,
  152. task_group: TaskGroup | None,
  153. task_params: collections.abc.MutableMapping | None,
  154. task_default_args: dict | None,
  155. ) -> tuple[dict, ParamsDict]:
  156. args, params = _get_parent_defaults(dag, task_group)
  157. if task_params:
  158. if not isinstance(task_params, collections.abc.Mapping):
  159. raise TypeError("params must be a mapping")
  160. params.update(task_params)
  161. if task_default_args:
  162. if not isinstance(task_default_args, collections.abc.Mapping):
  163. raise TypeError("default_args must be a mapping")
  164. args.update(task_default_args)
  165. with contextlib.suppress(KeyError):
  166. params.update(task_default_args["params"] or {})
  167. return args, params
  168. class _PartialDescriptor:
  169. """A descriptor that guards against ``.partial`` being called on Task objects."""
  170. class_method: ClassMethodDescriptorType | None = None
  171. def __get__(
  172. self, obj: BaseOperator, cls: type[BaseOperator] | None = None
  173. ) -> Callable[..., OperatorPartial]:
  174. # Call this "partial" so it looks nicer in stack traces.
  175. def partial(**kwargs):
  176. raise TypeError("partial can only be called on Operator classes, not Tasks themselves")
  177. if obj is not None:
  178. return partial
  179. return self.class_method.__get__(cls, cls)
  180. _PARTIAL_DEFAULTS: dict[str, Any] = {
  181. "map_index_template": None,
  182. "owner": DEFAULT_OWNER,
  183. "trigger_rule": DEFAULT_TRIGGER_RULE,
  184. "depends_on_past": False,
  185. "ignore_first_depends_on_past": DEFAULT_IGNORE_FIRST_DEPENDS_ON_PAST,
  186. "wait_for_past_depends_before_skipping": DEFAULT_WAIT_FOR_PAST_DEPENDS_BEFORE_SKIPPING,
  187. "wait_for_downstream": False,
  188. "retries": DEFAULT_RETRIES,
  189. "executor": DEFAULT_EXECUTOR,
  190. "queue": DEFAULT_QUEUE,
  191. "pool_slots": DEFAULT_POOL_SLOTS,
  192. "execution_timeout": DEFAULT_TASK_EXECUTION_TIMEOUT,
  193. "retry_delay": DEFAULT_RETRY_DELAY,
  194. "retry_exponential_backoff": False,
  195. "priority_weight": DEFAULT_PRIORITY_WEIGHT,
  196. "weight_rule": DEFAULT_WEIGHT_RULE,
  197. "inlets": [],
  198. "outlets": [],
  199. "allow_nested_operators": True,
  200. }
  201. # This is what handles the actual mapping.
  202. def partial(
  203. operator_class: type[BaseOperator],
  204. *,
  205. task_id: str,
  206. dag: DAG | None = None,
  207. task_group: TaskGroup | None = None,
  208. start_date: datetime | ArgNotSet = NOTSET,
  209. end_date: datetime | ArgNotSet = NOTSET,
  210. owner: str | ArgNotSet = NOTSET,
  211. email: None | str | Iterable[str] | ArgNotSet = NOTSET,
  212. params: collections.abc.MutableMapping | None = None,
  213. resources: dict[str, Any] | None | ArgNotSet = NOTSET,
  214. trigger_rule: str | ArgNotSet = NOTSET,
  215. depends_on_past: bool | ArgNotSet = NOTSET,
  216. ignore_first_depends_on_past: bool | ArgNotSet = NOTSET,
  217. wait_for_past_depends_before_skipping: bool | ArgNotSet = NOTSET,
  218. wait_for_downstream: bool | ArgNotSet = NOTSET,
  219. retries: int | None | ArgNotSet = NOTSET,
  220. queue: str | ArgNotSet = NOTSET,
  221. pool: str | ArgNotSet = NOTSET,
  222. pool_slots: int | ArgNotSet = NOTSET,
  223. execution_timeout: timedelta | None | ArgNotSet = NOTSET,
  224. max_retry_delay: None | timedelta | float | ArgNotSet = NOTSET,
  225. retry_delay: timedelta | float | ArgNotSet = NOTSET,
  226. retry_exponential_backoff: bool | ArgNotSet = NOTSET,
  227. priority_weight: int | ArgNotSet = NOTSET,
  228. weight_rule: str | PriorityWeightStrategy | ArgNotSet = NOTSET,
  229. sla: timedelta | None | ArgNotSet = NOTSET,
  230. map_index_template: str | None | ArgNotSet = NOTSET,
  231. max_active_tis_per_dag: int | None | ArgNotSet = NOTSET,
  232. max_active_tis_per_dagrun: int | None | ArgNotSet = NOTSET,
  233. on_execute_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] | ArgNotSet = NOTSET,
  234. on_failure_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] | ArgNotSet = NOTSET,
  235. on_success_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] | ArgNotSet = NOTSET,
  236. on_retry_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] | ArgNotSet = NOTSET,
  237. on_skipped_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] | ArgNotSet = NOTSET,
  238. run_as_user: str | None | ArgNotSet = NOTSET,
  239. executor: str | None | ArgNotSet = NOTSET,
  240. executor_config: dict | None | ArgNotSet = NOTSET,
  241. inlets: Any | None | ArgNotSet = NOTSET,
  242. outlets: Any | None | ArgNotSet = NOTSET,
  243. doc: str | None | ArgNotSet = NOTSET,
  244. doc_md: str | None | ArgNotSet = NOTSET,
  245. doc_json: str | None | ArgNotSet = NOTSET,
  246. doc_yaml: str | None | ArgNotSet = NOTSET,
  247. doc_rst: str | None | ArgNotSet = NOTSET,
  248. task_display_name: str | None | ArgNotSet = NOTSET,
  249. logger_name: str | None | ArgNotSet = NOTSET,
  250. allow_nested_operators: bool = True,
  251. **kwargs,
  252. ) -> OperatorPartial:
  253. from airflow.models.dag import DagContext
  254. from airflow.utils.task_group import TaskGroupContext
  255. validate_mapping_kwargs(operator_class, "partial", kwargs)
  256. dag = dag or DagContext.get_current_dag()
  257. if dag:
  258. task_group = task_group or TaskGroupContext.get_current_task_group(dag)
  259. if task_group:
  260. task_id = task_group.child_id(task_id)
  261. # Merge DAG and task group level defaults into user-supplied values.
  262. dag_default_args, partial_params = get_merged_defaults(
  263. dag=dag,
  264. task_group=task_group,
  265. task_params=params,
  266. task_default_args=kwargs.pop("default_args", None),
  267. )
  268. # Create partial_kwargs from args and kwargs
  269. partial_kwargs: dict[str, Any] = {
  270. **kwargs,
  271. "dag": dag,
  272. "task_group": task_group,
  273. "task_id": task_id,
  274. "map_index_template": map_index_template,
  275. "start_date": start_date,
  276. "end_date": end_date,
  277. "owner": owner,
  278. "email": email,
  279. "trigger_rule": trigger_rule,
  280. "depends_on_past": depends_on_past,
  281. "ignore_first_depends_on_past": ignore_first_depends_on_past,
  282. "wait_for_past_depends_before_skipping": wait_for_past_depends_before_skipping,
  283. "wait_for_downstream": wait_for_downstream,
  284. "retries": retries,
  285. "queue": queue,
  286. "pool": pool,
  287. "pool_slots": pool_slots,
  288. "execution_timeout": execution_timeout,
  289. "max_retry_delay": max_retry_delay,
  290. "retry_delay": retry_delay,
  291. "retry_exponential_backoff": retry_exponential_backoff,
  292. "priority_weight": priority_weight,
  293. "weight_rule": weight_rule,
  294. "sla": sla,
  295. "max_active_tis_per_dag": max_active_tis_per_dag,
  296. "max_active_tis_per_dagrun": max_active_tis_per_dagrun,
  297. "on_execute_callback": on_execute_callback,
  298. "on_failure_callback": on_failure_callback,
  299. "on_retry_callback": on_retry_callback,
  300. "on_success_callback": on_success_callback,
  301. "on_skipped_callback": on_skipped_callback,
  302. "run_as_user": run_as_user,
  303. "executor": executor,
  304. "executor_config": executor_config,
  305. "inlets": inlets,
  306. "outlets": outlets,
  307. "resources": resources,
  308. "doc": doc,
  309. "doc_json": doc_json,
  310. "doc_md": doc_md,
  311. "doc_rst": doc_rst,
  312. "doc_yaml": doc_yaml,
  313. "task_display_name": task_display_name,
  314. "logger_name": logger_name,
  315. "allow_nested_operators": allow_nested_operators,
  316. }
  317. # Inject DAG-level default args into args provided to this function.
  318. partial_kwargs.update((k, v) for k, v in dag_default_args.items() if partial_kwargs.get(k) is NOTSET)
  319. # Fill fields not provided by the user with default values.
  320. partial_kwargs = {k: _PARTIAL_DEFAULTS.get(k) if v is NOTSET else v for k, v in partial_kwargs.items()}
  321. # Post-process arguments. Should be kept in sync with _TaskDecorator.expand().
  322. if "task_concurrency" in kwargs: # Reject deprecated option.
  323. raise TypeError("unexpected argument: task_concurrency")
  324. if partial_kwargs["wait_for_downstream"]:
  325. partial_kwargs["depends_on_past"] = True
  326. partial_kwargs["start_date"] = timezone.convert_to_utc(partial_kwargs["start_date"])
  327. partial_kwargs["end_date"] = timezone.convert_to_utc(partial_kwargs["end_date"])
  328. if partial_kwargs["pool"] is None:
  329. partial_kwargs["pool"] = Pool.DEFAULT_POOL_NAME
  330. if partial_kwargs["pool_slots"] < 1:
  331. dag_str = ""
  332. if dag:
  333. dag_str = f" in dag {dag.dag_id}"
  334. raise ValueError(f"pool slots for {task_id}{dag_str} cannot be less than 1")
  335. partial_kwargs["retries"] = parse_retries(partial_kwargs["retries"])
  336. partial_kwargs["retry_delay"] = coerce_timedelta(partial_kwargs["retry_delay"], key="retry_delay")
  337. if partial_kwargs["max_retry_delay"] is not None:
  338. partial_kwargs["max_retry_delay"] = coerce_timedelta(
  339. partial_kwargs["max_retry_delay"],
  340. key="max_retry_delay",
  341. )
  342. partial_kwargs["executor_config"] = partial_kwargs["executor_config"] or {}
  343. partial_kwargs["resources"] = coerce_resources(partial_kwargs["resources"])
  344. return OperatorPartial(
  345. operator_class=operator_class,
  346. kwargs=partial_kwargs,
  347. params=partial_params,
  348. )
  349. class ExecutorSafeguard:
  350. """
  351. The ExecutorSafeguard decorator.
  352. Checks if the execute method of an operator isn't manually called outside
  353. the TaskInstance as we want to avoid bad mixing between decorated and
  354. classic operators.
  355. """
  356. test_mode = conf.getboolean("core", "unit_test_mode")
  357. _sentinel = local()
  358. _sentinel.callers = {}
  359. @classmethod
  360. def decorator(cls, func):
  361. @wraps(func)
  362. def wrapper(self, *args, **kwargs):
  363. from airflow.decorators.base import DecoratedOperator
  364. sentinel_key = f"{self.__class__.__name__}__sentinel"
  365. sentinel = kwargs.pop(sentinel_key, None)
  366. if sentinel:
  367. if not getattr(cls._sentinel, "callers", None):
  368. cls._sentinel.callers = {}
  369. cls._sentinel.callers[sentinel_key] = sentinel
  370. else:
  371. sentinel = cls._sentinel.callers.pop(f"{func.__qualname__.split('.')[0]}__sentinel", None)
  372. if not cls.test_mode and not sentinel == _sentinel and not isinstance(self, DecoratedOperator):
  373. message = f"{self.__class__.__name__}.{func.__name__} cannot be called outside TaskInstance!"
  374. if not self.allow_nested_operators:
  375. raise AirflowException(message)
  376. self.log.warning(message)
  377. return func(self, *args, **kwargs)
  378. return wrapper
  379. class BaseOperatorMeta(abc.ABCMeta):
  380. """Metaclass of BaseOperator."""
  381. @classmethod
  382. def _apply_defaults(cls, func: T) -> T:
  383. """
  384. Look for an argument named "default_args", and fill the unspecified arguments from it.
  385. Since python2.* isn't clear about which arguments are missing when
  386. calling a function, and that this can be quite confusing with multi-level
  387. inheritance and argument defaults, this decorator also alerts with
  388. specific information about the missing arguments.
  389. """
  390. # Cache inspect.signature for the wrapper closure to avoid calling it
  391. # at every decorated invocation. This is separate sig_cache created
  392. # per decoration, i.e. each function decorated using apply_defaults will
  393. # have a different sig_cache.
  394. sig_cache = inspect.signature(func)
  395. non_variadic_params = {
  396. name: param
  397. for (name, param) in sig_cache.parameters.items()
  398. if param.name != "self" and param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)
  399. }
  400. non_optional_args = {
  401. name
  402. for name, param in non_variadic_params.items()
  403. if param.default == param.empty and name != "task_id"
  404. }
  405. fixup_decorator_warning_stack(func)
  406. @wraps(func)
  407. def apply_defaults(self: BaseOperator, *args: Any, **kwargs: Any) -> Any:
  408. from airflow.models.dag import DagContext
  409. from airflow.utils.task_group import TaskGroupContext
  410. if args:
  411. raise AirflowException("Use keyword arguments when initializing operators")
  412. instantiated_from_mapped = kwargs.pop(
  413. "_airflow_from_mapped",
  414. getattr(self, "_BaseOperator__from_mapped", False),
  415. )
  416. dag: DAG | None = kwargs.get("dag") or DagContext.get_current_dag()
  417. task_group: TaskGroup | None = kwargs.get("task_group")
  418. if dag and not task_group:
  419. task_group = TaskGroupContext.get_current_task_group(dag)
  420. default_args, merged_params = get_merged_defaults(
  421. dag=dag,
  422. task_group=task_group,
  423. task_params=kwargs.pop("params", None),
  424. task_default_args=kwargs.pop("default_args", None),
  425. )
  426. for arg in sig_cache.parameters:
  427. if arg not in kwargs and arg in default_args:
  428. kwargs[arg] = default_args[arg]
  429. missing_args = non_optional_args.difference(kwargs)
  430. if len(missing_args) == 1:
  431. raise AirflowException(f"missing keyword argument {missing_args.pop()!r}")
  432. elif missing_args:
  433. display = ", ".join(repr(a) for a in sorted(missing_args))
  434. raise AirflowException(f"missing keyword arguments {display}")
  435. if merged_params:
  436. kwargs["params"] = merged_params
  437. hook = getattr(self, "_hook_apply_defaults", None)
  438. if hook:
  439. args, kwargs = hook(**kwargs, default_args=default_args)
  440. default_args = kwargs.pop("default_args", {})
  441. if not hasattr(self, "_BaseOperator__init_kwargs"):
  442. self._BaseOperator__init_kwargs = {}
  443. self._BaseOperator__from_mapped = instantiated_from_mapped
  444. result = func(self, **kwargs, default_args=default_args)
  445. # Store the args passed to init -- we need them to support task.map serialization!
  446. self._BaseOperator__init_kwargs.update(kwargs) # type: ignore
  447. # Set upstream task defined by XComArgs passed to template fields of the operator.
  448. # BUT: only do this _ONCE_, not once for each class in the hierarchy
  449. if not instantiated_from_mapped and func == self.__init__.__wrapped__: # type: ignore[misc]
  450. self.set_xcomargs_dependencies()
  451. # Mark instance as instantiated.
  452. self._BaseOperator__instantiated = True
  453. return result
  454. apply_defaults.__non_optional_args = non_optional_args # type: ignore
  455. apply_defaults.__param_names = set(non_variadic_params) # type: ignore
  456. return cast(T, apply_defaults)
  457. def __new__(cls, name, bases, namespace, **kwargs):
  458. execute_method = namespace.get("execute")
  459. if callable(execute_method) and not getattr(execute_method, "__isabstractmethod__", False):
  460. namespace["execute"] = ExecutorSafeguard().decorator(execute_method)
  461. new_cls = super().__new__(cls, name, bases, namespace, **kwargs)
  462. with contextlib.suppress(KeyError):
  463. # Update the partial descriptor with the class method, so it calls the actual function
  464. # (but let subclasses override it if they need to)
  465. partial_desc = vars(new_cls)["partial"]
  466. if isinstance(partial_desc, _PartialDescriptor):
  467. partial_desc.class_method = classmethod(partial)
  468. # We patch `__init__` only if the class defines it.
  469. if inspect.getmro(new_cls)[1].__init__ is not new_cls.__init__:
  470. new_cls.__init__ = cls._apply_defaults(new_cls.__init__)
  471. return new_cls
  472. # TODO: The following mapping is used to validate that the arguments passed to the BaseOperator are of the
  473. # correct type. This is a temporary solution until we find a more sophisticated method for argument
  474. # validation. One potential method is to use `get_type_hints` from the typing module. However, this is not
  475. # fully compatible with future annotations for Python versions below 3.10. Once we require a minimum Python
  476. # version that supports `get_type_hints` effectively or find a better approach, we can replace this
  477. # manual type-checking method.
  478. BASEOPERATOR_ARGS_EXPECTED_TYPES = {
  479. "task_id": str,
  480. "email": (str, Iterable),
  481. "email_on_retry": bool,
  482. "email_on_failure": bool,
  483. "retries": int,
  484. "retry_exponential_backoff": bool,
  485. "depends_on_past": bool,
  486. "ignore_first_depends_on_past": bool,
  487. "wait_for_past_depends_before_skipping": bool,
  488. "wait_for_downstream": bool,
  489. "priority_weight": int,
  490. "queue": str,
  491. "pool": str,
  492. "pool_slots": int,
  493. "trigger_rule": str,
  494. "run_as_user": str,
  495. "task_concurrency": int,
  496. "map_index_template": str,
  497. "max_active_tis_per_dag": int,
  498. "max_active_tis_per_dagrun": int,
  499. "executor": str,
  500. "do_xcom_push": bool,
  501. "multiple_outputs": bool,
  502. "doc": str,
  503. "doc_md": str,
  504. "doc_json": str,
  505. "doc_yaml": str,
  506. "doc_rst": str,
  507. "task_display_name": str,
  508. "logger_name": str,
  509. "allow_nested_operators": bool,
  510. }
  511. @total_ordering
  512. class BaseOperator(AbstractOperator, metaclass=BaseOperatorMeta):
  513. r"""
  514. Abstract base class for all operators.
  515. Since operators create objects that become nodes in the DAG, BaseOperator
  516. contains many recursive methods for DAG crawling behavior. To derive from
  517. this class, you are expected to override the constructor and the 'execute'
  518. method.
  519. Operators derived from this class should perform or trigger certain tasks
  520. synchronously (wait for completion). Example of operators could be an
  521. operator that runs a Pig job (PigOperator), a sensor operator that
  522. waits for a partition to land in Hive (HiveSensorOperator), or one that
  523. moves data from Hive to MySQL (Hive2MySqlOperator). Instances of these
  524. operators (tasks) target specific operations, running specific scripts,
  525. functions or data transfers.
  526. This class is abstract and shouldn't be instantiated. Instantiating a
  527. class derived from this one results in the creation of a task object,
  528. which ultimately becomes a node in DAG objects. Task dependencies should
  529. be set by using the set_upstream and/or set_downstream methods.
  530. :param task_id: a unique, meaningful id for the task
  531. :param owner: the owner of the task. Using a meaningful description
  532. (e.g. user/person/team/role name) to clarify ownership is recommended.
  533. :param email: the 'to' email address(es) used in email alerts. This can be a
  534. single email or multiple ones. Multiple addresses can be specified as a
  535. comma or semicolon separated string or by passing a list of strings.
  536. :param email_on_retry: Indicates whether email alerts should be sent when a
  537. task is retried
  538. :param email_on_failure: Indicates whether email alerts should be sent when
  539. a task failed
  540. :param retries: the number of retries that should be performed before
  541. failing the task
  542. :param retry_delay: delay between retries, can be set as ``timedelta`` or
  543. ``float`` seconds, which will be converted into ``timedelta``,
  544. the default is ``timedelta(seconds=300)``.
  545. :param retry_exponential_backoff: allow progressively longer waits between
  546. retries by using exponential backoff algorithm on retry delay (delay
  547. will be converted into seconds)
  548. :param max_retry_delay: maximum delay interval between retries, can be set as
  549. ``timedelta`` or ``float`` seconds, which will be converted into ``timedelta``.
  550. :param start_date: The ``start_date`` for the task, determines
  551. the ``execution_date`` for the first task instance. The best practice
  552. is to have the start_date rounded
  553. to your DAG's ``schedule_interval``. Daily jobs have their start_date
  554. some day at 00:00:00, hourly jobs have their start_date at 00:00
  555. of a specific hour. Note that Airflow simply looks at the latest
  556. ``execution_date`` and adds the ``schedule_interval`` to determine
  557. the next ``execution_date``. It is also very important
  558. to note that different tasks' dependencies
  559. need to line up in time. If task A depends on task B and their
  560. start_date are offset in a way that their execution_date don't line
  561. up, A's dependencies will never be met. If you are looking to delay
  562. a task, for example running a daily task at 2AM, look into the
  563. ``TimeSensor`` and ``TimeDeltaSensor``. We advise against using
  564. dynamic ``start_date`` and recommend using fixed ones. Read the
  565. FAQ entry about start_date for more information.
  566. :param end_date: if specified, the scheduler won't go beyond this date
  567. :param depends_on_past: when set to true, task instances will run
  568. sequentially and only if the previous instance has succeeded or has been skipped.
  569. The task instance for the start_date is allowed to run.
  570. :param wait_for_past_depends_before_skipping: when set to true, if the task instance
  571. should be marked as skipped, and depends_on_past is true, the ti will stay on None state
  572. waiting the task of the previous run
  573. :param wait_for_downstream: when set to true, an instance of task
  574. X will wait for tasks immediately downstream of the previous instance
  575. of task X to finish successfully or be skipped before it runs. This is useful if the
  576. different instances of a task X alter the same asset, and this asset
  577. is used by tasks downstream of task X. Note that depends_on_past
  578. is forced to True wherever wait_for_downstream is used. Also note that
  579. only tasks *immediately* downstream of the previous task instance are waited
  580. for; the statuses of any tasks further downstream are ignored.
  581. :param dag: a reference to the dag the task is attached to (if any)
  582. :param priority_weight: priority weight of this task against other task.
  583. This allows the executor to trigger higher priority tasks before
  584. others when things get backed up. Set priority_weight as a higher
  585. number for more important tasks.
  586. As not all database engines support 64-bit integers, values are capped with 32-bit.
  587. Valid range is from -2,147,483,648 to 2,147,483,647.
  588. :param weight_rule: weighting method used for the effective total
  589. priority weight of the task. Options are:
  590. ``{ downstream | upstream | absolute }`` default is ``downstream``
  591. When set to ``downstream`` the effective weight of the task is the
  592. aggregate sum of all downstream descendants. As a result, upstream
  593. tasks will have higher weight and will be scheduled more aggressively
  594. when using positive weight values. This is useful when you have
  595. multiple dag run instances and desire to have all upstream tasks to
  596. complete for all runs before each dag can continue processing
  597. downstream tasks. When set to ``upstream`` the effective weight is the
  598. aggregate sum of all upstream ancestors. This is the opposite where
  599. downstream tasks have higher weight and will be scheduled more
  600. aggressively when using positive weight values. This is useful when you
  601. have multiple dag run instances and prefer to have each dag complete
  602. before starting upstream tasks of other dags. When set to
  603. ``absolute``, the effective weight is the exact ``priority_weight``
  604. specified without additional weighting. You may want to do this when
  605. you know exactly what priority weight each task should have.
  606. Additionally, when set to ``absolute``, there is bonus effect of
  607. significantly speeding up the task creation process as for very large
  608. DAGs. Options can be set as string or using the constants defined in
  609. the static class ``airflow.utils.WeightRule``.
  610. Irrespective of the weight rule, resulting priority values are capped with 32-bit.
  611. |experimental|
  612. Since 2.9.0, Airflow allows to define custom priority weight strategy,
  613. by creating a subclass of
  614. ``airflow.task.priority_strategy.PriorityWeightStrategy`` and registering
  615. in a plugin, then providing the class path or the class instance via
  616. ``weight_rule`` parameter. The custom priority weight strategy will be
  617. used to calculate the effective total priority weight of the task instance.
  618. :param queue: which queue to target when running this job. Not
  619. all executors implement queue management, the CeleryExecutor
  620. does support targeting specific queues.
  621. :param pool: the slot pool this task should run in, slot pools are a
  622. way to limit concurrency for certain tasks
  623. :param pool_slots: the number of pool slots this task should use (>= 1)
  624. Values less than 1 are not allowed.
  625. :param sla: time by which the job is expected to succeed. Note that
  626. this represents the ``timedelta`` after the period is closed. For
  627. example if you set an SLA of 1 hour, the scheduler would send an email
  628. soon after 1:00AM on the ``2016-01-02`` if the ``2016-01-01`` instance
  629. has not succeeded yet.
  630. The scheduler pays special attention for jobs with an SLA and
  631. sends alert
  632. emails for SLA misses. SLA misses are also recorded in the database
  633. for future reference. All tasks that share the same SLA time
  634. get bundled in a single email, sent soon after that time. SLA
  635. notification are sent once and only once for each task instance.
  636. :param execution_timeout: max time allowed for the execution of
  637. this task instance, if it goes beyond it will raise and fail.
  638. :param on_failure_callback: a function or list of functions to be called when a task instance
  639. of this task fails. a context dictionary is passed as a single
  640. parameter to this function. Context contains references to related
  641. objects to the task instance and is documented under the macros
  642. section of the API.
  643. :param on_execute_callback: much like the ``on_failure_callback`` except
  644. that it is executed right before the task is executed.
  645. :param on_retry_callback: much like the ``on_failure_callback`` except
  646. that it is executed when retries occur.
  647. :param on_success_callback: much like the ``on_failure_callback`` except
  648. that it is executed when the task succeeds.
  649. :param on_skipped_callback: much like the ``on_failure_callback`` except
  650. that it is executed when skipped occur; this callback will be called only if AirflowSkipException get raised.
  651. Explicitly it is NOT called if a task is not started to be executed because of a preceding branching
  652. decision in the DAG or a trigger rule which causes execution to skip so that the task execution
  653. is never scheduled.
  654. :param pre_execute: a function to be called immediately before task
  655. execution, receiving a context dictionary; raising an exception will
  656. prevent the task from being executed.
  657. |experimental|
  658. :param post_execute: a function to be called immediately after task
  659. execution, receiving a context dictionary and task result; raising an
  660. exception will prevent the task from succeeding.
  661. |experimental|
  662. :param trigger_rule: defines the rule by which dependencies are applied
  663. for the task to get triggered. Options are:
  664. ``{ all_success | all_failed | all_done | all_skipped | one_success | one_done |
  665. one_failed | none_failed | none_failed_min_one_success | none_skipped | always}``
  666. default is ``all_success``. Options can be set as string or
  667. using the constants defined in the static class
  668. ``airflow.utils.TriggerRule``
  669. :param resources: A map of resource parameter names (the argument names of the
  670. Resources constructor) to their values.
  671. :param run_as_user: unix username to impersonate while running the task
  672. :param max_active_tis_per_dag: When set, a task will be able to limit the concurrent
  673. runs across execution_dates.
  674. :param max_active_tis_per_dagrun: When set, a task will be able to limit the concurrent
  675. task instances per DAG run.
  676. :param executor: Which executor to target when running this task. NOT YET SUPPORTED
  677. :param executor_config: Additional task-level configuration parameters that are
  678. interpreted by a specific executor. Parameters are namespaced by the name of
  679. executor.
  680. **Example**: to run this task in a specific docker container through
  681. the KubernetesExecutor ::
  682. MyOperator(..., executor_config={"KubernetesExecutor": {"image": "myCustomDockerImage"}})
  683. :param do_xcom_push: if True, an XCom is pushed containing the Operator's
  684. result
  685. :param multiple_outputs: if True and do_xcom_push is True, pushes multiple XComs, one for each
  686. key in the returned dictionary result. If False and do_xcom_push is True, pushes a single XCom.
  687. :param task_group: The TaskGroup to which the task should belong. This is typically provided when not
  688. using a TaskGroup as a context manager.
  689. :param doc: Add documentation or notes to your Task objects that is visible in
  690. Task Instance details View in the Webserver
  691. :param doc_md: Add documentation (in Markdown format) or notes to your Task objects
  692. that is visible in Task Instance details View in the Webserver
  693. :param doc_rst: Add documentation (in RST format) or notes to your Task objects
  694. that is visible in Task Instance details View in the Webserver
  695. :param doc_json: Add documentation (in JSON format) or notes to your Task objects
  696. that is visible in Task Instance details View in the Webserver
  697. :param doc_yaml: Add documentation (in YAML format) or notes to your Task objects
  698. that is visible in Task Instance details View in the Webserver
  699. :param task_display_name: The display name of the task which appears on the UI.
  700. :param logger_name: Name of the logger used by the Operator to emit logs.
  701. If set to `None` (default), the logger name will fall back to
  702. `airflow.task.operators.{class.__module__}.{class.__name__}` (e.g. SimpleHttpOperator will have
  703. *airflow.task.operators.airflow.providers.http.operators.http.SimpleHttpOperator* as logger).
  704. :param allow_nested_operators: if True, when an operator is executed within another one a warning message
  705. will be logged. If False, then an exception will be raised if the operator is badly used (e.g. nested
  706. within another one). In future releases of Airflow this parameter will be removed and an exception
  707. will always be thrown when operators are nested within each other (default is True).
  708. **Example**: example of a bad operator mixin usage::
  709. @task(provide_context=True)
  710. def say_hello_world(**context):
  711. hello_world_task = BashOperator(
  712. task_id="hello_world_task",
  713. bash_command="python -c \"print('Hello, world!')\"",
  714. dag=dag,
  715. )
  716. hello_world_task.execute(context)
  717. """
  718. # Implementing Operator.
  719. template_fields: Sequence[str] = ()
  720. template_ext: Sequence[str] = ()
  721. template_fields_renderers: dict[str, str] = {}
  722. # Defines the color in the UI
  723. ui_color: str = "#fff"
  724. ui_fgcolor: str = "#000"
  725. pool: str = ""
  726. # base list which includes all the attrs that don't need deep copy.
  727. _base_operator_shallow_copy_attrs: tuple[str, ...] = (
  728. "user_defined_macros",
  729. "user_defined_filters",
  730. "params",
  731. )
  732. # each operator should override this class attr for shallow copy attrs.
  733. shallow_copy_attrs: Sequence[str] = ()
  734. # Defines the operator level extra links
  735. operator_extra_links: Collection[BaseOperatorLink] = ()
  736. # The _serialized_fields are lazily loaded when get_serialized_fields() method is called
  737. __serialized_fields: frozenset[str] | None = None
  738. partial: Callable[..., OperatorPartial] = _PartialDescriptor() # type: ignore
  739. _comps = {
  740. "task_id",
  741. "dag_id",
  742. "owner",
  743. "email",
  744. "email_on_retry",
  745. "retry_delay",
  746. "retry_exponential_backoff",
  747. "max_retry_delay",
  748. "start_date",
  749. "end_date",
  750. "depends_on_past",
  751. "wait_for_downstream",
  752. "priority_weight",
  753. "sla",
  754. "execution_timeout",
  755. "on_execute_callback",
  756. "on_failure_callback",
  757. "on_success_callback",
  758. "on_retry_callback",
  759. "on_skipped_callback",
  760. "do_xcom_push",
  761. "multiple_outputs",
  762. "allow_nested_operators",
  763. "executor",
  764. }
  765. # Defines if the operator supports lineage without manual definitions
  766. supports_lineage = False
  767. # If True then the class constructor was called
  768. __instantiated = False
  769. # List of args as passed to `init()`, after apply_defaults() has been updated. Used to "recreate" the task
  770. # when mapping
  771. __init_kwargs: dict[str, Any]
  772. # Set to True before calling execute method
  773. _lock_for_execution = False
  774. _dag: DAG | None = None
  775. task_group: TaskGroup | None = None
  776. # subdag parameter is only set for SubDagOperator.
  777. # Setting it to None by default as other Operators do not have that field
  778. subdag: DAG | None = None
  779. start_date: pendulum.DateTime | None = None
  780. end_date: pendulum.DateTime | None = None
  781. # Set to True for an operator instantiated by a mapped operator.
  782. __from_mapped = False
  783. start_trigger_args: StartTriggerArgs | None = None
  784. start_from_trigger: bool = False
  785. def __init__(
  786. self,
  787. task_id: str,
  788. owner: str = DEFAULT_OWNER,
  789. email: str | Iterable[str] | None = None,
  790. email_on_retry: bool = conf.getboolean("email", "default_email_on_retry", fallback=True),
  791. email_on_failure: bool = conf.getboolean("email", "default_email_on_failure", fallback=True),
  792. retries: int | None = DEFAULT_RETRIES,
  793. retry_delay: timedelta | float = DEFAULT_RETRY_DELAY,
  794. retry_exponential_backoff: bool = False,
  795. max_retry_delay: timedelta | float | None = None,
  796. start_date: datetime | None = None,
  797. end_date: datetime | None = None,
  798. depends_on_past: bool = False,
  799. ignore_first_depends_on_past: bool = DEFAULT_IGNORE_FIRST_DEPENDS_ON_PAST,
  800. wait_for_past_depends_before_skipping: bool = DEFAULT_WAIT_FOR_PAST_DEPENDS_BEFORE_SKIPPING,
  801. wait_for_downstream: bool = False,
  802. dag: DAG | None = None,
  803. params: collections.abc.MutableMapping | None = None,
  804. default_args: dict | None = None,
  805. priority_weight: int = DEFAULT_PRIORITY_WEIGHT,
  806. weight_rule: str | PriorityWeightStrategy = DEFAULT_WEIGHT_RULE,
  807. queue: str = DEFAULT_QUEUE,
  808. pool: str | None = None,
  809. pool_slots: int = DEFAULT_POOL_SLOTS,
  810. sla: timedelta | None = None,
  811. execution_timeout: timedelta | None = DEFAULT_TASK_EXECUTION_TIMEOUT,
  812. on_execute_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] = None,
  813. on_failure_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] = None,
  814. on_success_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] = None,
  815. on_retry_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] = None,
  816. on_skipped_callback: None | TaskStateChangeCallback | list[TaskStateChangeCallback] = None,
  817. pre_execute: TaskPreExecuteHook | None = None,
  818. post_execute: TaskPostExecuteHook | None = None,
  819. trigger_rule: str = DEFAULT_TRIGGER_RULE,
  820. resources: dict[str, Any] | None = None,
  821. run_as_user: str | None = None,
  822. task_concurrency: int | None = None,
  823. map_index_template: str | None = None,
  824. max_active_tis_per_dag: int | None = None,
  825. max_active_tis_per_dagrun: int | None = None,
  826. executor: str | None = None,
  827. executor_config: dict | None = None,
  828. do_xcom_push: bool = True,
  829. multiple_outputs: bool = False,
  830. inlets: Any | None = None,
  831. outlets: Any | None = None,
  832. task_group: TaskGroup | None = None,
  833. doc: str | None = None,
  834. doc_md: str | None = None,
  835. doc_json: str | None = None,
  836. doc_yaml: str | None = None,
  837. doc_rst: str | None = None,
  838. task_display_name: str | None = None,
  839. logger_name: str | None = None,
  840. allow_nested_operators: bool = True,
  841. **kwargs,
  842. ):
  843. from airflow.models.dag import DagContext
  844. from airflow.utils.task_group import TaskGroupContext
  845. self.__init_kwargs = {}
  846. super().__init__()
  847. kwargs.pop("_airflow_mapped_validation_only", None)
  848. if kwargs:
  849. if not conf.getboolean("operators", "ALLOW_ILLEGAL_ARGUMENTS"):
  850. raise AirflowException(
  851. f"Invalid arguments were passed to {self.__class__.__name__} (task_id: {task_id}). "
  852. f"Invalid arguments were:\n**kwargs: {kwargs}",
  853. )
  854. warnings.warn(
  855. f"Invalid arguments were passed to {self.__class__.__name__} (task_id: {task_id}). "
  856. "Support for passing such arguments will be dropped in future. "
  857. f"Invalid arguments were:\n**kwargs: {kwargs}",
  858. category=RemovedInAirflow3Warning,
  859. stacklevel=3,
  860. )
  861. dag = dag or DagContext.get_current_dag()
  862. task_group = task_group or TaskGroupContext.get_current_task_group(dag)
  863. self.task_id = task_group.child_id(task_id) if task_group else task_id
  864. validate_key(self.task_id)
  865. if not self.__from_mapped and task_group:
  866. task_group.add(self)
  867. self.owner = owner
  868. self.email = email
  869. self.email_on_retry = email_on_retry
  870. self.email_on_failure = email_on_failure
  871. if execution_timeout is not None and not isinstance(execution_timeout, timedelta):
  872. raise ValueError(
  873. f"execution_timeout must be timedelta object but passed as type: {type(execution_timeout)}"
  874. )
  875. self.execution_timeout = execution_timeout
  876. self.on_execute_callback = on_execute_callback
  877. self.on_failure_callback = on_failure_callback
  878. self.on_success_callback = on_success_callback
  879. self.on_retry_callback = on_retry_callback
  880. self.on_skipped_callback = on_skipped_callback
  881. self._pre_execute_hook = pre_execute
  882. self._post_execute_hook = post_execute
  883. if start_date and not isinstance(start_date, datetime):
  884. self.log.warning("start_date for %s isn't datetime.datetime", self)
  885. elif start_date:
  886. self.start_date = timezone.convert_to_utc(start_date)
  887. if end_date:
  888. self.end_date = timezone.convert_to_utc(end_date)
  889. self.executor = executor
  890. self.executor_config = executor_config or {}
  891. self.run_as_user = run_as_user
  892. self.retries = parse_retries(retries)
  893. self.queue = queue
  894. self.pool = Pool.DEFAULT_POOL_NAME if pool is None else pool
  895. self.pool_slots = pool_slots
  896. if self.pool_slots < 1:
  897. dag_str = f" in dag {dag.dag_id}" if dag else ""
  898. raise ValueError(f"pool slots for {self.task_id}{dag_str} cannot be less than 1")
  899. self.sla = sla
  900. if trigger_rule == "dummy":
  901. warnings.warn(
  902. "dummy Trigger Rule is deprecated. Please use `TriggerRule.ALWAYS`.",
  903. RemovedInAirflow3Warning,
  904. stacklevel=2,
  905. )
  906. trigger_rule = TriggerRule.ALWAYS
  907. if trigger_rule == "none_failed_or_skipped":
  908. warnings.warn(
  909. "none_failed_or_skipped Trigger Rule is deprecated. "
  910. "Please use `none_failed_min_one_success`.",
  911. RemovedInAirflow3Warning,
  912. stacklevel=2,
  913. )
  914. trigger_rule = TriggerRule.NONE_FAILED_MIN_ONE_SUCCESS
  915. if not TriggerRule.is_valid(trigger_rule):
  916. raise AirflowException(
  917. f"The trigger_rule must be one of {TriggerRule.all_triggers()},"
  918. f"'{dag.dag_id if dag else ''}.{task_id}'; received '{trigger_rule}'."
  919. )
  920. self.trigger_rule: TriggerRule = TriggerRule(trigger_rule)
  921. FailStopDagInvalidTriggerRule.check(dag=dag, trigger_rule=self.trigger_rule)
  922. self.depends_on_past: bool = depends_on_past
  923. self.ignore_first_depends_on_past: bool = ignore_first_depends_on_past
  924. self.wait_for_past_depends_before_skipping: bool = wait_for_past_depends_before_skipping
  925. self.wait_for_downstream: bool = wait_for_downstream
  926. if wait_for_downstream:
  927. self.depends_on_past = True
  928. self.retry_delay = coerce_timedelta(retry_delay, key="retry_delay")
  929. self.retry_exponential_backoff = retry_exponential_backoff
  930. self.max_retry_delay = (
  931. max_retry_delay
  932. if max_retry_delay is None
  933. else coerce_timedelta(max_retry_delay, key="max_retry_delay")
  934. )
  935. # At execution_time this becomes a normal dict
  936. self.params: ParamsDict | dict = ParamsDict(params)
  937. if priority_weight is not None and not isinstance(priority_weight, int):
  938. raise AirflowException(
  939. f"`priority_weight` for task '{self.task_id}' only accepts integers, "
  940. f"received '{type(priority_weight)}'."
  941. )
  942. self.priority_weight = priority_weight
  943. self.weight_rule = validate_and_load_priority_weight_strategy(weight_rule)
  944. self.resources = coerce_resources(resources)
  945. if task_concurrency and not max_active_tis_per_dag:
  946. # TODO: Remove in Airflow 3.0
  947. warnings.warn(
  948. "The 'task_concurrency' parameter is deprecated. Please use 'max_active_tis_per_dag'.",
  949. RemovedInAirflow3Warning,
  950. stacklevel=2,
  951. )
  952. max_active_tis_per_dag = task_concurrency
  953. self.max_active_tis_per_dag: int | None = max_active_tis_per_dag
  954. self.max_active_tis_per_dagrun: int | None = max_active_tis_per_dagrun
  955. self.do_xcom_push: bool = do_xcom_push
  956. self.map_index_template: str | None = map_index_template
  957. self.multiple_outputs: bool = multiple_outputs
  958. self.doc_md = doc_md
  959. self.doc_json = doc_json
  960. self.doc_yaml = doc_yaml
  961. self.doc_rst = doc_rst
  962. self.doc = doc
  963. # Populate the display field only if provided and different from task id
  964. self._task_display_property_value = (
  965. task_display_name if task_display_name and task_display_name != task_id else None
  966. )
  967. self.upstream_task_ids: set[str] = set()
  968. self.downstream_task_ids: set[str] = set()
  969. if dag:
  970. self.dag = dag
  971. self._log_config_logger_name = "airflow.task.operators"
  972. self._logger_name = logger_name
  973. self.allow_nested_operators: bool = allow_nested_operators
  974. # Lineage
  975. self.inlets: list = []
  976. self.outlets: list = []
  977. if inlets:
  978. self.inlets = (
  979. inlets
  980. if isinstance(inlets, list)
  981. else [
  982. inlets,
  983. ]
  984. )
  985. if outlets:
  986. self.outlets = (
  987. outlets
  988. if isinstance(outlets, list)
  989. else [
  990. outlets,
  991. ]
  992. )
  993. if isinstance(self.template_fields, str):
  994. warnings.warn(
  995. f"The `template_fields` value for {self.task_type} is a string "
  996. "but should be a list or tuple of string. Wrapping it in a list for execution. "
  997. f"Please update {self.task_type} accordingly.",
  998. UserWarning,
  999. stacklevel=2,
  1000. )
  1001. self.template_fields = [self.template_fields]
  1002. self._is_setup = False
  1003. self._is_teardown = False
  1004. if SetupTeardownContext.active:
  1005. SetupTeardownContext.update_context_map(self)
  1006. validate_instance_args(self, BASEOPERATOR_ARGS_EXPECTED_TYPES)
  1007. def __eq__(self, other):
  1008. if type(self) is type(other):
  1009. # Use getattr() instead of __dict__ as __dict__ doesn't return
  1010. # correct values for properties.
  1011. return all(getattr(self, c, None) == getattr(other, c, None) for c in self._comps)
  1012. return False
  1013. def __ne__(self, other):
  1014. return not self == other
  1015. def __hash__(self):
  1016. hash_components = [type(self)]
  1017. for component in self._comps:
  1018. val = getattr(self, component, None)
  1019. try:
  1020. hash(val)
  1021. hash_components.append(val)
  1022. except TypeError:
  1023. hash_components.append(repr(val))
  1024. return hash(tuple(hash_components))
  1025. # including lineage information
  1026. def __or__(self, other):
  1027. """
  1028. Return [This Operator] | [Operator].
  1029. The inlets of other will be set to pick up the outlets from this operator.
  1030. Other will be set as a downstream task of this operator.
  1031. """
  1032. if isinstance(other, BaseOperator):
  1033. if not self.outlets and not self.supports_lineage:
  1034. raise ValueError("No outlets defined for this operator")
  1035. other.add_inlets([self.task_id])
  1036. self.set_downstream(other)
  1037. else:
  1038. raise TypeError(f"Right hand side ({other}) is not an Operator")
  1039. return self
  1040. # /Composing Operators ---------------------------------------------
  1041. def __gt__(self, other):
  1042. """
  1043. Return [Operator] > [Outlet].
  1044. If other is an attr annotated object it is set as an outlet of this Operator.
  1045. """
  1046. if not isinstance(other, Iterable):
  1047. other = [other]
  1048. for obj in other:
  1049. if not attr.has(obj):
  1050. raise TypeError(f"Left hand side ({obj}) is not an outlet")
  1051. self.add_outlets(other)
  1052. return self
  1053. def __lt__(self, other):
  1054. """
  1055. Return [Inlet] > [Operator] or [Operator] < [Inlet].
  1056. If other is an attr annotated object it is set as an inlet to this operator.
  1057. """
  1058. if not isinstance(other, Iterable):
  1059. other = [other]
  1060. for obj in other:
  1061. if not attr.has(obj):
  1062. raise TypeError(f"{obj} cannot be an inlet")
  1063. self.add_inlets(other)
  1064. return self
  1065. def __setattr__(self, key, value):
  1066. super().__setattr__(key, value)
  1067. if self.__from_mapped or self._lock_for_execution:
  1068. return # Skip any custom behavior for validation and during execute.
  1069. if key in self.__init_kwargs:
  1070. self.__init_kwargs[key] = value
  1071. if self.__instantiated and key in self.template_fields:
  1072. # Resolve upstreams set by assigning an XComArg after initializing
  1073. # an operator, example:
  1074. # op = BashOperator()
  1075. # op.bash_command = "sleep 1"
  1076. self.set_xcomargs_dependencies()
  1077. def add_inlets(self, inlets: Iterable[Any]):
  1078. """Set inlets to this operator."""
  1079. self.inlets.extend(inlets)
  1080. def add_outlets(self, outlets: Iterable[Any]):
  1081. """Define the outlets of this operator."""
  1082. self.outlets.extend(outlets)
  1083. def get_inlet_defs(self):
  1084. """
  1085. Get inlet definitions on this task.
  1086. :meta private:
  1087. """
  1088. return self.inlets
  1089. def get_outlet_defs(self):
  1090. """
  1091. Get outlet definitions on this task.
  1092. :meta private:
  1093. """
  1094. return self.outlets
  1095. def get_dag(self) -> DAG | None:
  1096. return self._dag
  1097. @property # type: ignore[override]
  1098. def dag(self) -> DAG: # type: ignore[override]
  1099. """Returns the Operator's DAG if set, otherwise raises an error."""
  1100. if self._dag:
  1101. return self._dag
  1102. else:
  1103. raise AirflowException(f"Operator {self} has not been assigned to a DAG yet")
  1104. @dag.setter
  1105. def dag(self, dag: DAG | None):
  1106. """Operators can be assigned to one DAG, one time. Repeat assignments to that same DAG are ok."""
  1107. if dag is None:
  1108. self._dag = None
  1109. return
  1110. # if set to removed, then just set and exit
  1111. if self._dag.__class__ is AttributeRemoved:
  1112. self._dag = dag
  1113. return
  1114. # if setting to removed, then just set and exit
  1115. if dag.__class__ is AttributeRemoved:
  1116. self._dag = AttributeRemoved("_dag") # type: ignore[assignment]
  1117. return
  1118. from airflow.models.dag import DAG
  1119. if not isinstance(dag, DAG):
  1120. raise TypeError(f"Expected DAG; received {dag.__class__.__name__}")
  1121. elif self.has_dag() and self.dag is not dag:
  1122. raise AirflowException(f"The DAG assigned to {self} can not be changed.")
  1123. if self.__from_mapped:
  1124. pass # Don't add to DAG -- the mapped task takes the place.
  1125. elif dag.task_dict.get(self.task_id) is not self:
  1126. dag.add_task(self)
  1127. self._dag = dag
  1128. @property
  1129. def task_display_name(self) -> str:
  1130. return self._task_display_property_value or self.task_id
  1131. def has_dag(self):
  1132. """Return True if the Operator has been assigned to a DAG."""
  1133. return self._dag is not None
  1134. deps: frozenset[BaseTIDep] = frozenset(
  1135. {
  1136. NotInRetryPeriodDep(),
  1137. PrevDagrunDep(),
  1138. TriggerRuleDep(),
  1139. NotPreviouslySkippedDep(),
  1140. MappedTaskUpstreamDep(),
  1141. }
  1142. )
  1143. """
  1144. Returns the set of dependencies for the operator. These differ from execution
  1145. context dependencies in that they are specific to tasks and can be
  1146. extended/overridden by subclasses.
  1147. """
  1148. def prepare_for_execution(self) -> BaseOperator:
  1149. """Lock task for execution to disable custom action in ``__setattr__`` and return a copy."""
  1150. other = copy.copy(self)
  1151. other._lock_for_execution = True
  1152. return other
  1153. def set_xcomargs_dependencies(self) -> None:
  1154. """
  1155. Resolve upstream dependencies of a task.
  1156. In this way passing an ``XComArg`` as value for a template field
  1157. will result in creating upstream relation between two tasks.
  1158. **Example**: ::
  1159. with DAG(...):
  1160. generate_content = GenerateContentOperator(task_id="generate_content")
  1161. send_email = EmailOperator(..., html_content=generate_content.output)
  1162. # This is equivalent to
  1163. with DAG(...):
  1164. generate_content = GenerateContentOperator(task_id="generate_content")
  1165. send_email = EmailOperator(..., html_content="{{ task_instance.xcom_pull('generate_content') }}")
  1166. generate_content >> send_email
  1167. """
  1168. from airflow.models.xcom_arg import XComArg
  1169. for field in self.template_fields:
  1170. if hasattr(self, field):
  1171. arg = getattr(self, field)
  1172. XComArg.apply_upstream_relationship(self, arg)
  1173. @prepare_lineage
  1174. def pre_execute(self, context: Any):
  1175. """Execute right before self.execute() is called."""
  1176. if self._pre_execute_hook is None:
  1177. return
  1178. ExecutionCallableRunner(
  1179. self._pre_execute_hook,
  1180. context_get_outlet_events(context),
  1181. logger=self.log,
  1182. ).run(context)
  1183. def execute(self, context: Context) -> Any:
  1184. """
  1185. Derive when creating an operator.
  1186. Context is the same dictionary used as when rendering jinja templates.
  1187. Refer to get_template_context for more context.
  1188. """
  1189. raise NotImplementedError()
  1190. @apply_lineage
  1191. def post_execute(self, context: Any, result: Any = None):
  1192. """
  1193. Execute right after self.execute() is called.
  1194. It is passed the execution context and any results returned by the operator.
  1195. """
  1196. if self._post_execute_hook is None:
  1197. return
  1198. ExecutionCallableRunner(
  1199. self._post_execute_hook,
  1200. context_get_outlet_events(context),
  1201. logger=self.log,
  1202. ).run(context, result)
  1203. def on_kill(self) -> None:
  1204. """
  1205. Override this method to clean up subprocesses when a task instance gets killed.
  1206. Any use of the threading, subprocess or multiprocessing module within an
  1207. operator needs to be cleaned up, or it will leave ghost processes behind.
  1208. """
  1209. def __deepcopy__(self, memo):
  1210. # Hack sorting double chained task lists by task_id to avoid hitting
  1211. # max_depth on deepcopy operations.
  1212. sys.setrecursionlimit(5000) # TODO fix this in a better way
  1213. cls = self.__class__
  1214. result = cls.__new__(cls)
  1215. memo[id(self)] = result
  1216. shallow_copy = cls.shallow_copy_attrs + cls._base_operator_shallow_copy_attrs
  1217. for k, v in self.__dict__.items():
  1218. if k == "_BaseOperator__instantiated":
  1219. # Don't set this until the _end_, as it changes behaviour of __setattr__
  1220. continue
  1221. if k not in shallow_copy:
  1222. setattr(result, k, copy.deepcopy(v, memo))
  1223. else:
  1224. setattr(result, k, copy.copy(v))
  1225. result.__instantiated = self.__instantiated
  1226. return result
  1227. def __getstate__(self):
  1228. state = dict(self.__dict__)
  1229. if self._log:
  1230. del state["_log"]
  1231. return state
  1232. def __setstate__(self, state):
  1233. self.__dict__ = state
  1234. def render_template_fields(
  1235. self,
  1236. context: Context,
  1237. jinja_env: jinja2.Environment | None = None,
  1238. ) -> None:
  1239. """
  1240. Template all attributes listed in *self.template_fields*.
  1241. This mutates the attributes in-place and is irreversible.
  1242. :param context: Context dict with values to apply on content.
  1243. :param jinja_env: Jinja's environment to use for rendering.
  1244. """
  1245. if not jinja_env:
  1246. jinja_env = self.get_template_env()
  1247. self._do_render_template_fields(self, self.template_fields, context, jinja_env, set())
  1248. @provide_session
  1249. def clear(
  1250. self,
  1251. start_date: datetime | None = None,
  1252. end_date: datetime | None = None,
  1253. upstream: bool = False,
  1254. downstream: bool = False,
  1255. session: Session = NEW_SESSION,
  1256. ):
  1257. """Clear the state of task instances associated with the task, following the parameters specified."""
  1258. qry = select(TaskInstance).where(TaskInstance.dag_id == self.dag_id)
  1259. if start_date:
  1260. qry = qry.where(TaskInstance.execution_date >= start_date)
  1261. if end_date:
  1262. qry = qry.where(TaskInstance.execution_date <= end_date)
  1263. tasks = [self.task_id]
  1264. if upstream:
  1265. tasks += [t.task_id for t in self.get_flat_relatives(upstream=True)]
  1266. if downstream:
  1267. tasks += [t.task_id for t in self.get_flat_relatives(upstream=False)]
  1268. qry = qry.where(TaskInstance.task_id.in_(tasks))
  1269. results = session.scalars(qry).all()
  1270. count = len(results)
  1271. clear_task_instances(results, session, dag=self.dag)
  1272. session.commit()
  1273. return count
  1274. @provide_session
  1275. def get_task_instances(
  1276. self,
  1277. start_date: datetime | None = None,
  1278. end_date: datetime | None = None,
  1279. session: Session = NEW_SESSION,
  1280. ) -> list[TaskInstance]:
  1281. """Get task instances related to this task for a specific date range."""
  1282. from airflow.models import DagRun
  1283. query = (
  1284. select(TaskInstance)
  1285. .join(TaskInstance.dag_run)
  1286. .where(TaskInstance.dag_id == self.dag_id)
  1287. .where(TaskInstance.task_id == self.task_id)
  1288. )
  1289. if start_date:
  1290. query = query.where(DagRun.execution_date >= start_date)
  1291. if end_date:
  1292. query = query.where(DagRun.execution_date <= end_date)
  1293. return session.scalars(query.order_by(DagRun.execution_date)).all()
  1294. @provide_session
  1295. def run(
  1296. self,
  1297. start_date: datetime | None = None,
  1298. end_date: datetime | None = None,
  1299. ignore_first_depends_on_past: bool = True,
  1300. wait_for_past_depends_before_skipping: bool = False,
  1301. ignore_ti_state: bool = False,
  1302. mark_success: bool = False,
  1303. test_mode: bool = False,
  1304. session: Session = NEW_SESSION,
  1305. ) -> None:
  1306. """Run a set of task instances for a date range."""
  1307. from airflow.models import DagRun
  1308. from airflow.utils.types import DagRunType
  1309. # Assertions for typing -- we need a dag, for this function, and when we have a DAG we are
  1310. # _guaranteed_ to have start_date (else we couldn't have been added to a DAG)
  1311. if TYPE_CHECKING:
  1312. assert self.start_date
  1313. start_date = pendulum.instance(start_date or self.start_date)
  1314. end_date = pendulum.instance(end_date or self.end_date or timezone.utcnow())
  1315. for info in self.dag.iter_dagrun_infos_between(start_date, end_date, align=False):
  1316. ignore_depends_on_past = info.logical_date == start_date and ignore_first_depends_on_past
  1317. try:
  1318. dag_run = session.scalars(
  1319. select(DagRun).where(
  1320. DagRun.dag_id == self.dag_id,
  1321. DagRun.execution_date == info.logical_date,
  1322. )
  1323. ).one()
  1324. ti = TaskInstance(self, run_id=dag_run.run_id)
  1325. except NoResultFound:
  1326. # This is _mostly_ only used in tests
  1327. dr = DagRun(
  1328. dag_id=self.dag_id,
  1329. run_id=DagRun.generate_run_id(DagRunType.MANUAL, info.logical_date),
  1330. run_type=DagRunType.MANUAL,
  1331. execution_date=info.logical_date,
  1332. data_interval=info.data_interval,
  1333. )
  1334. ti = TaskInstance(self, run_id=dr.run_id)
  1335. session.add(ti)
  1336. ti.dag_run = dr
  1337. session.add(dr)
  1338. session.flush()
  1339. session.commit()
  1340. ti.run(
  1341. mark_success=mark_success,
  1342. ignore_depends_on_past=ignore_depends_on_past,
  1343. wait_for_past_depends_before_skipping=wait_for_past_depends_before_skipping,
  1344. ignore_ti_state=ignore_ti_state,
  1345. test_mode=test_mode,
  1346. session=session,
  1347. )
  1348. def dry_run(self) -> None:
  1349. """Perform dry run for the operator - just render template fields."""
  1350. self.log.info("Dry run")
  1351. for field in self.template_fields:
  1352. try:
  1353. content = getattr(self, field)
  1354. except AttributeError:
  1355. raise AttributeError(
  1356. f"{field!r} is configured as a template field "
  1357. f"but {self.task_type} does not have this attribute."
  1358. )
  1359. if content and isinstance(content, str):
  1360. self.log.info("Rendering template for %s", field)
  1361. self.log.info(content)
  1362. def get_direct_relatives(self, upstream: bool = False) -> Iterable[Operator]:
  1363. """Get list of the direct relatives to the current task, upstream or downstream."""
  1364. if upstream:
  1365. return self.upstream_list
  1366. else:
  1367. return self.downstream_list
  1368. def __repr__(self):
  1369. return f"<Task({self.task_type}): {self.task_id}>"
  1370. @property
  1371. def operator_class(self) -> type[BaseOperator]: # type: ignore[override]
  1372. return self.__class__
  1373. @property
  1374. def task_type(self) -> str:
  1375. """@property: type of the task."""
  1376. return self.__class__.__name__
  1377. @property
  1378. def operator_name(self) -> str:
  1379. """@property: use a more friendly display name for the operator, if set."""
  1380. try:
  1381. return self.custom_operator_name # type: ignore
  1382. except AttributeError:
  1383. return self.task_type
  1384. @property
  1385. def roots(self) -> list[BaseOperator]:
  1386. """Required by DAGNode."""
  1387. return [self]
  1388. @property
  1389. def leaves(self) -> list[BaseOperator]:
  1390. """Required by DAGNode."""
  1391. return [self]
  1392. @property
  1393. def output(self) -> XComArg:
  1394. """Returns reference to XCom pushed by current operator."""
  1395. from airflow.models.xcom_arg import XComArg
  1396. return XComArg(operator=self)
  1397. @property
  1398. def is_setup(self) -> bool:
  1399. """
  1400. Whether the operator is a setup task.
  1401. :meta private:
  1402. """
  1403. return self._is_setup
  1404. @is_setup.setter
  1405. def is_setup(self, value: bool) -> None:
  1406. """
  1407. Setter for is_setup property.
  1408. :meta private:
  1409. """
  1410. if self.is_teardown and value:
  1411. raise ValueError(f"Cannot mark task '{self.task_id}' as setup; task is already a teardown.")
  1412. self._is_setup = value
  1413. @property
  1414. def is_teardown(self) -> bool:
  1415. """
  1416. Whether the operator is a teardown task.
  1417. :meta private:
  1418. """
  1419. return self._is_teardown
  1420. @is_teardown.setter
  1421. def is_teardown(self, value: bool) -> None:
  1422. """
  1423. Setter for is_teardown property.
  1424. :meta private:
  1425. """
  1426. if self.is_setup and value:
  1427. raise ValueError(f"Cannot mark task '{self.task_id}' as teardown; task is already a setup.")
  1428. self._is_teardown = value
  1429. @staticmethod
  1430. def xcom_push(
  1431. context: Any,
  1432. key: str,
  1433. value: Any,
  1434. execution_date: datetime | None = None,
  1435. ) -> None:
  1436. """
  1437. Make an XCom available for tasks to pull.
  1438. :param context: Execution Context Dictionary
  1439. :param key: A key for the XCom
  1440. :param value: A value for the XCom. The value is pickled and stored
  1441. in the database.
  1442. :param execution_date: if provided, the XCom will not be visible until
  1443. this date. This can be used, for example, to send a message to a
  1444. task on a future date without it being immediately visible.
  1445. """
  1446. context["ti"].xcom_push(key=key, value=value, execution_date=execution_date)
  1447. @staticmethod
  1448. @provide_session
  1449. def xcom_pull(
  1450. context: Any,
  1451. task_ids: str | list[str] | None = None,
  1452. dag_id: str | None = None,
  1453. key: str = XCOM_RETURN_KEY,
  1454. include_prior_dates: bool | None = None,
  1455. session: Session = NEW_SESSION,
  1456. ) -> Any:
  1457. """
  1458. Pull XComs that optionally meet certain criteria.
  1459. The default value for `key` limits the search to XComs
  1460. that were returned by other tasks (as opposed to those that were pushed
  1461. manually). To remove this filter, pass key=None (or any desired value).
  1462. If a single task_id string is provided, the result is the value of the
  1463. most recent matching XCom from that task_id. If multiple task_ids are
  1464. provided, a tuple of matching values is returned. None is returned
  1465. whenever no matches are found.
  1466. :param context: Execution Context Dictionary
  1467. :param key: A key for the XCom. If provided, only XComs with matching
  1468. keys will be returned. The default key is 'return_value', also
  1469. available as a constant XCOM_RETURN_KEY. This key is automatically
  1470. given to XComs returned by tasks (as opposed to being pushed
  1471. manually). To remove the filter, pass key=None.
  1472. :param task_ids: Only XComs from tasks with matching ids will be
  1473. pulled. Can pass None to remove the filter.
  1474. :param dag_id: If provided, only pulls XComs from this DAG.
  1475. If None (default), the DAG of the calling task is used.
  1476. :param include_prior_dates: If False, only XComs from the current
  1477. execution_date are returned. If True, XComs from previous dates
  1478. are returned as well.
  1479. """
  1480. return context["ti"].xcom_pull(
  1481. key=key,
  1482. task_ids=task_ids,
  1483. dag_id=dag_id,
  1484. include_prior_dates=include_prior_dates,
  1485. session=session,
  1486. )
  1487. @classmethod
  1488. def get_serialized_fields(cls):
  1489. """Stringified DAGs and operators contain exactly these fields."""
  1490. if not cls.__serialized_fields:
  1491. from airflow.models.dag import DagContext
  1492. # make sure the following dummy task is not added to current active
  1493. # dag in context, otherwise, it will result in
  1494. # `RuntimeError: dictionary changed size during iteration`
  1495. # Exception in SerializedDAG.serialize_dag() call.
  1496. DagContext.push_context_managed_dag(None)
  1497. cls.__serialized_fields = frozenset(
  1498. vars(BaseOperator(task_id="test")).keys()
  1499. - {
  1500. "upstream_task_ids",
  1501. "default_args",
  1502. "dag",
  1503. "_dag",
  1504. "label",
  1505. "_BaseOperator__instantiated",
  1506. "_BaseOperator__init_kwargs",
  1507. "_BaseOperator__from_mapped",
  1508. "_is_setup",
  1509. "_is_teardown",
  1510. "_on_failure_fail_dagrun",
  1511. }
  1512. | { # Class level defaults need to be added to this list
  1513. "start_date",
  1514. "end_date",
  1515. "_task_type",
  1516. "_operator_name",
  1517. "subdag",
  1518. "ui_color",
  1519. "ui_fgcolor",
  1520. "template_ext",
  1521. "template_fields",
  1522. "template_fields_renderers",
  1523. "params",
  1524. "is_setup",
  1525. "is_teardown",
  1526. "on_failure_fail_dagrun",
  1527. "map_index_template",
  1528. "start_trigger_args",
  1529. "_needs_expansion",
  1530. "start_from_trigger",
  1531. }
  1532. )
  1533. DagContext.pop_context_managed_dag()
  1534. return cls.__serialized_fields
  1535. def serialize_for_task_group(self) -> tuple[DagAttributeTypes, Any]:
  1536. """Serialize; required by DAGNode."""
  1537. return DagAttributeTypes.OP, self.task_id
  1538. @property
  1539. def inherits_from_empty_operator(self):
  1540. """Used to determine if an Operator is inherited from EmptyOperator."""
  1541. # This looks like `isinstance(self, EmptyOperator) would work, but this also
  1542. # needs to cope when `self` is a Serialized instance of a EmptyOperator or one
  1543. # of its subclasses (which don't inherit from anything but BaseOperator).
  1544. return getattr(self, "_is_empty", False)
  1545. def defer(
  1546. self,
  1547. *,
  1548. trigger: BaseTrigger,
  1549. method_name: str,
  1550. kwargs: dict[str, Any] | None = None,
  1551. timeout: timedelta | None = None,
  1552. ) -> NoReturn:
  1553. """
  1554. Mark this Operator "deferred", suspending its execution until the provided trigger fires an event.
  1555. This is achieved by raising a special exception (TaskDeferred)
  1556. which is caught in the main _execute_task wrapper. Triggers can send execution back to task or end
  1557. the task instance directly. If the trigger will end the task instance itself, ``method_name`` should
  1558. be None; otherwise, provide the name of the method that should be used when resuming execution in
  1559. the task.
  1560. """
  1561. raise TaskDeferred(trigger=trigger, method_name=method_name, kwargs=kwargs, timeout=timeout)
  1562. def resume_execution(self, next_method: str, next_kwargs: dict[str, Any] | None, context: Context):
  1563. """Call this method when a deferred task is resumed."""
  1564. # __fail__ is a special signal value for next_method that indicates
  1565. # this task was scheduled specifically to fail.
  1566. if next_method == "__fail__":
  1567. next_kwargs = next_kwargs or {}
  1568. traceback = next_kwargs.get("traceback")
  1569. if traceback is not None:
  1570. self.log.error("Trigger failed:\n%s", "\n".join(traceback))
  1571. raise TaskDeferralError(next_kwargs.get("error", "Unknown"))
  1572. # Grab the callable off the Operator/Task and add in any kwargs
  1573. execute_callable = getattr(self, next_method)
  1574. if next_kwargs:
  1575. execute_callable = functools.partial(execute_callable, **next_kwargs)
  1576. return execute_callable(context)
  1577. def unmap(self, resolve: None | dict[str, Any] | tuple[Context, Session]) -> BaseOperator:
  1578. """
  1579. Get the "normal" operator from the current operator.
  1580. Since a BaseOperator is not mapped to begin with, this simply returns
  1581. the original operator.
  1582. :meta private:
  1583. """
  1584. return self
  1585. def expand_start_from_trigger(self, *, context: Context, session: Session) -> bool:
  1586. """
  1587. Get the start_from_trigger value of the current abstract operator.
  1588. Since a BaseOperator is not mapped to begin with, this simply returns
  1589. the original value of start_from_trigger.
  1590. :meta private:
  1591. """
  1592. return self.start_from_trigger
  1593. def expand_start_trigger_args(self, *, context: Context, session: Session) -> StartTriggerArgs | None:
  1594. """
  1595. Get the start_trigger_args value of the current abstract operator.
  1596. Since a BaseOperator is not mapped to begin with, this simply returns
  1597. the original value of start_trigger_args.
  1598. :meta private:
  1599. """
  1600. return self.start_trigger_args
  1601. # TODO: Deprecate for Airflow 3.0
  1602. Chainable = Union[DependencyMixin, Sequence[DependencyMixin]]
  1603. def chain(*tasks: DependencyMixin | Sequence[DependencyMixin]) -> None:
  1604. r"""
  1605. Given a number of tasks, builds a dependency chain.
  1606. This function accepts values of BaseOperator (aka tasks), EdgeModifiers (aka Labels), XComArg, TaskGroups,
  1607. or lists containing any mix of these types (or a mix in the same list). If you want to chain between two
  1608. lists you must ensure they have the same length.
  1609. Using classic operators/sensors:
  1610. .. code-block:: python
  1611. chain(t1, [t2, t3], [t4, t5], t6)
  1612. is equivalent to::
  1613. / -> t2 -> t4 \
  1614. t1 -> t6
  1615. \ -> t3 -> t5 /
  1616. .. code-block:: python
  1617. t1.set_downstream(t2)
  1618. t1.set_downstream(t3)
  1619. t2.set_downstream(t4)
  1620. t3.set_downstream(t5)
  1621. t4.set_downstream(t6)
  1622. t5.set_downstream(t6)
  1623. Using task-decorated functions aka XComArgs:
  1624. .. code-block:: python
  1625. chain(x1(), [x2(), x3()], [x4(), x5()], x6())
  1626. is equivalent to::
  1627. / -> x2 -> x4 \
  1628. x1 -> x6
  1629. \ -> x3 -> x5 /
  1630. .. code-block:: python
  1631. x1 = x1()
  1632. x2 = x2()
  1633. x3 = x3()
  1634. x4 = x4()
  1635. x5 = x5()
  1636. x6 = x6()
  1637. x1.set_downstream(x2)
  1638. x1.set_downstream(x3)
  1639. x2.set_downstream(x4)
  1640. x3.set_downstream(x5)
  1641. x4.set_downstream(x6)
  1642. x5.set_downstream(x6)
  1643. Using TaskGroups:
  1644. .. code-block:: python
  1645. chain(t1, task_group1, task_group2, t2)
  1646. t1.set_downstream(task_group1)
  1647. task_group1.set_downstream(task_group2)
  1648. task_group2.set_downstream(t2)
  1649. It is also possible to mix between classic operator/sensor, EdgeModifiers, XComArg, and TaskGroups:
  1650. .. code-block:: python
  1651. chain(t1, [Label("branch one"), Label("branch two")], [x1(), x2()], task_group1, x3())
  1652. is equivalent to::
  1653. / "branch one" -> x1 \
  1654. t1 -> task_group1 -> x3
  1655. \ "branch two" -> x2 /
  1656. .. code-block:: python
  1657. x1 = x1()
  1658. x2 = x2()
  1659. x3 = x3()
  1660. label1 = Label("branch one")
  1661. label2 = Label("branch two")
  1662. t1.set_downstream(label1)
  1663. label1.set_downstream(x1)
  1664. t2.set_downstream(label2)
  1665. label2.set_downstream(x2)
  1666. x1.set_downstream(task_group1)
  1667. x2.set_downstream(task_group1)
  1668. task_group1.set_downstream(x3)
  1669. # or
  1670. x1 = x1()
  1671. x2 = x2()
  1672. x3 = x3()
  1673. t1.set_downstream(x1, edge_modifier=Label("branch one"))
  1674. t1.set_downstream(x2, edge_modifier=Label("branch two"))
  1675. x1.set_downstream(task_group1)
  1676. x2.set_downstream(task_group1)
  1677. task_group1.set_downstream(x3)
  1678. :param tasks: Individual and/or list of tasks, EdgeModifiers, XComArgs, or TaskGroups to set dependencies
  1679. """
  1680. for up_task, down_task in zip(tasks, tasks[1:]):
  1681. if isinstance(up_task, DependencyMixin):
  1682. up_task.set_downstream(down_task)
  1683. continue
  1684. if isinstance(down_task, DependencyMixin):
  1685. down_task.set_upstream(up_task)
  1686. continue
  1687. if not isinstance(up_task, Sequence) or not isinstance(down_task, Sequence):
  1688. raise TypeError(f"Chain not supported between instances of {type(up_task)} and {type(down_task)}")
  1689. up_task_list = up_task
  1690. down_task_list = down_task
  1691. if len(up_task_list) != len(down_task_list):
  1692. raise AirflowException(
  1693. f"Chain not supported for different length Iterable. "
  1694. f"Got {len(up_task_list)} and {len(down_task_list)}."
  1695. )
  1696. for up_t, down_t in zip(up_task_list, down_task_list):
  1697. up_t.set_downstream(down_t)
  1698. def cross_downstream(
  1699. from_tasks: Sequence[DependencyMixin],
  1700. to_tasks: DependencyMixin | Sequence[DependencyMixin],
  1701. ):
  1702. r"""
  1703. Set downstream dependencies for all tasks in from_tasks to all tasks in to_tasks.
  1704. Using classic operators/sensors:
  1705. .. code-block:: python
  1706. cross_downstream(from_tasks=[t1, t2, t3], to_tasks=[t4, t5, t6])
  1707. is equivalent to::
  1708. t1 ---> t4
  1709. \ /
  1710. t2 -X -> t5
  1711. / \
  1712. t3 ---> t6
  1713. .. code-block:: python
  1714. t1.set_downstream(t4)
  1715. t1.set_downstream(t5)
  1716. t1.set_downstream(t6)
  1717. t2.set_downstream(t4)
  1718. t2.set_downstream(t5)
  1719. t2.set_downstream(t6)
  1720. t3.set_downstream(t4)
  1721. t3.set_downstream(t5)
  1722. t3.set_downstream(t6)
  1723. Using task-decorated functions aka XComArgs:
  1724. .. code-block:: python
  1725. cross_downstream(from_tasks=[x1(), x2(), x3()], to_tasks=[x4(), x5(), x6()])
  1726. is equivalent to::
  1727. x1 ---> x4
  1728. \ /
  1729. x2 -X -> x5
  1730. / \
  1731. x3 ---> x6
  1732. .. code-block:: python
  1733. x1 = x1()
  1734. x2 = x2()
  1735. x3 = x3()
  1736. x4 = x4()
  1737. x5 = x5()
  1738. x6 = x6()
  1739. x1.set_downstream(x4)
  1740. x1.set_downstream(x5)
  1741. x1.set_downstream(x6)
  1742. x2.set_downstream(x4)
  1743. x2.set_downstream(x5)
  1744. x2.set_downstream(x6)
  1745. x3.set_downstream(x4)
  1746. x3.set_downstream(x5)
  1747. x3.set_downstream(x6)
  1748. It is also possible to mix between classic operator/sensor and XComArg tasks:
  1749. .. code-block:: python
  1750. cross_downstream(from_tasks=[t1, x2(), t3], to_tasks=[x1(), t2, x3()])
  1751. is equivalent to::
  1752. t1 ---> x1
  1753. \ /
  1754. x2 -X -> t2
  1755. / \
  1756. t3 ---> x3
  1757. .. code-block:: python
  1758. x1 = x1()
  1759. x2 = x2()
  1760. x3 = x3()
  1761. t1.set_downstream(x1)
  1762. t1.set_downstream(t2)
  1763. t1.set_downstream(x3)
  1764. x2.set_downstream(x1)
  1765. x2.set_downstream(t2)
  1766. x2.set_downstream(x3)
  1767. t3.set_downstream(x1)
  1768. t3.set_downstream(t2)
  1769. t3.set_downstream(x3)
  1770. :param from_tasks: List of tasks or XComArgs to start from.
  1771. :param to_tasks: List of tasks or XComArgs to set as downstream dependencies.
  1772. """
  1773. for task in from_tasks:
  1774. task.set_downstream(to_tasks)
  1775. def chain_linear(*elements: DependencyMixin | Sequence[DependencyMixin]):
  1776. """
  1777. Simplify task dependency definition.
  1778. E.g.: suppose you want precedence like so::
  1779. ╭─op2─╮ ╭─op4─╮
  1780. op1─┤ ├─├─op5─┤─op7
  1781. ╰-op3─╯ ╰-op6─╯
  1782. Then you can accomplish like so::
  1783. chain_linear(op1, [op2, op3], [op4, op5, op6], op7)
  1784. :param elements: a list of operators / lists of operators
  1785. """
  1786. if not elements:
  1787. raise ValueError("No tasks provided; nothing to do.")
  1788. prev_elem = None
  1789. deps_set = False
  1790. for curr_elem in elements:
  1791. if isinstance(curr_elem, EdgeModifier):
  1792. raise ValueError("Labels are not supported by chain_linear")
  1793. if prev_elem is not None:
  1794. for task in prev_elem:
  1795. task >> curr_elem
  1796. if not deps_set:
  1797. deps_set = True
  1798. prev_elem = [curr_elem] if isinstance(curr_elem, DependencyMixin) else curr_elem
  1799. if not deps_set:
  1800. raise ValueError("No dependencies were set. Did you forget to expand with `*`?")
  1801. def __getattr__(name):
  1802. """
  1803. PEP-562: Lazy loaded attributes on python modules.
  1804. :meta private:
  1805. """
  1806. path = __deprecated_imports.get(name)
  1807. if not path:
  1808. raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
  1809. from airflow.utils.module_loading import import_string
  1810. warnings.warn(
  1811. f"Import `{__name__}.{name}` is deprecated. Please use `{path}.{name}`.",
  1812. RemovedInAirflow3Warning,
  1813. stacklevel=2,
  1814. )
  1815. val = import_string(f"{path}.{name}")
  1816. # Store for next time
  1817. globals()[name] = val
  1818. return val
  1819. __deprecated_imports = {
  1820. "BaseOperatorLink": "airflow.models.baseoperatorlink",
  1821. }