apache-airflow-providers-edge3 1.4.0__py3-none-any.whl → 1.4.1__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 (25) hide show
  1. airflow/providers/edge3/__init__.py +1 -1
  2. airflow/providers/edge3/cli/api_client.py +9 -3
  3. airflow/providers/edge3/cli/worker.py +15 -9
  4. airflow/providers/edge3/executors/edge_executor.py +6 -4
  5. airflow/providers/edge3/models/edge_job.py +1 -1
  6. airflow/providers/edge3/models/edge_worker.py +21 -7
  7. airflow/providers/edge3/plugins/edge_executor_plugin.py +3 -12
  8. airflow/providers/edge3/plugins/www/dist/main.umd.cjs +14 -50
  9. airflow/providers/edge3/plugins/www/package.json +22 -22
  10. airflow/providers/edge3/plugins/www/pnpm-lock.yaml +1502 -1509
  11. airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx +9 -14
  12. airflow/providers/edge3/plugins/www/src/layouts/NavTabs.tsx +1 -8
  13. airflow/providers/edge3/plugins/www/src/main.tsx +0 -5
  14. airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx +7 -8
  15. airflow/providers/edge3/plugins/www/src/utils/index.ts +0 -1
  16. airflow/providers/edge3/plugins/www/vite.config.ts +2 -1
  17. airflow/providers/edge3/worker_api/routes/_v2_compat.py +1 -0
  18. airflow/providers/edge3/worker_api/routes/jobs.py +4 -5
  19. airflow/providers/edge3/worker_api/routes/ui.py +8 -3
  20. airflow/providers/edge3/worker_api/routes/worker.py +8 -4
  21. {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/METADATA +11 -11
  22. {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/RECORD +24 -25
  23. airflow/providers/edge3/plugins/www/src/utils/tokenHandler.ts +0 -51
  24. {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/WHEEL +0 -0
  25. {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/entry_points.txt +0 -0
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "1.4.0"
32
+ __version__ = "1.4.1"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.10.0"
@@ -53,12 +53,18 @@ logger = logging.getLogger(__name__)
53
53
  # Note: Given defaults make attempts after 1, 3, 7, 15, 31seconds, 1:03, 2:07, 3:37 and fails after 5:07min
54
54
  # So far there is no other config facility in Task SDK we use ENV for the moment
55
55
  # TODO: Consider these env variables jointly in task sdk together with task_sdk/src/airflow/sdk/api/client.py
56
- API_RETRIES = int(os.getenv("AIRFLOW__EDGE__API_RETRIES", os.getenv("AIRFLOW__WORKERS__API_RETRIES", 10)))
56
+ API_RETRIES = int(
57
+ os.getenv("AIRFLOW__EDGE__API_RETRIES", os.getenv("AIRFLOW__WORKERS__API_RETRIES", str(10)))
58
+ )
57
59
  API_RETRY_WAIT_MIN = float(
58
- os.getenv("AIRFLOW__EDGE__API_RETRY_WAIT_MIN", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MIN", 1.0))
60
+ os.getenv(
61
+ "AIRFLOW__EDGE__API_RETRY_WAIT_MIN", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MIN", str(1.0))
62
+ )
59
63
  )
60
64
  API_RETRY_WAIT_MAX = float(
61
- os.getenv("AIRFLOW__EDGE__API_RETRY_WAIT_MAX", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MAX", 90.0))
65
+ os.getenv(
66
+ "AIRFLOW__EDGE__API_RETRY_WAIT_MAX", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MAX", str(90.0))
67
+ )
62
68
  )
63
69
 
64
70
 
@@ -21,13 +21,13 @@ import os
21
21
  import signal
22
22
  import sys
23
23
  from datetime import datetime
24
+ from functools import cache
24
25
  from http import HTTPStatus
25
26
  from multiprocessing import Process
26
27
  from pathlib import Path
27
28
  from subprocess import Popen
28
29
  from time import sleep
29
30
  from typing import TYPE_CHECKING
30
- from urllib.parse import urlparse
31
31
 
32
32
  from lockfile.pidlockfile import remove_existing_pidfile
33
33
  from requests import HTTPError
@@ -176,7 +176,19 @@ class EdgeWorker:
176
176
  return EdgeWorkerState.IDLE
177
177
 
178
178
  @staticmethod
179
- def _run_job_via_supervisor(workload) -> int:
179
+ @cache
180
+ def _execution_api_server_url() -> str:
181
+ """Get the execution api server url from config or environment."""
182
+ api_url = conf.get("edge", "api_url")
183
+ execution_api_server_url = conf.get("core", "execution_api_server_url", fallback="")
184
+ if not execution_api_server_url and api_url:
185
+ # Derive execution api url from edge api url as fallback
186
+ execution_api_server_url = api_url.replace("edge_worker/v1/rpcapi", "execution")
187
+ logger.info("Using execution api server url: %s", execution_api_server_url)
188
+ return execution_api_server_url
189
+
190
+ @staticmethod
191
+ def _run_job_via_supervisor(workload, execution_api_server_url) -> int:
180
192
  from airflow.sdk.execution_time.supervisor import supervise
181
193
 
182
194
  # Ignore ctrl-c in this process -- we don't want to kill _this_ one. we let tasks run to completion
@@ -186,12 +198,6 @@ class EdgeWorker:
186
198
  setproctitle(f"airflow edge worker: {workload.ti.key}")
187
199
 
188
200
  try:
189
- api_url = conf.get("edge", "api_url")
190
- execution_api_server_url = conf.get("core", "execution_api_server_url", fallback="")
191
- if not execution_api_server_url:
192
- parsed = urlparse(api_url)
193
- execution_api_server_url = f"{parsed.scheme}://{parsed.netloc}/execution/"
194
-
195
201
  supervise(
196
202
  # This is the "wrong" ti type, but it duck types the same. TODO: Create a protocol for this.
197
203
  # Same like in airflow/executors/local_executor.py:_execute_work()
@@ -215,7 +221,7 @@ class EdgeWorker:
215
221
  workload: ExecuteTask = edge_job.command
216
222
  process = Process(
217
223
  target=EdgeWorker._run_job_via_supervisor,
218
- kwargs={"workload": workload},
224
+ kwargs={"workload": workload, "execution_api_server_url": EdgeWorker._execution_api_server_url()},
219
225
  )
220
226
  process.start()
221
227
  base_log_folder = conf.get("logging", "base_log_folder", fallback="NOT AVAILABLE")
@@ -30,7 +30,7 @@ from sqlalchemy.orm import Session
30
30
  from airflow.cli.cli_config import GroupCommand
31
31
  from airflow.configuration import conf
32
32
  from airflow.executors.base_executor import BaseExecutor
33
- from airflow.models.taskinstance import TaskInstance, TaskInstanceState
33
+ from airflow.models.taskinstance import TaskInstance
34
34
  from airflow.providers.common.compat.sdk import timezone
35
35
  from airflow.providers.edge3.cli.edge_command import EDGE_COMMANDS
36
36
  from airflow.providers.edge3.models.edge_job import EdgeJobModel
@@ -40,6 +40,7 @@ from airflow.providers.edge3.version_compat import AIRFLOW_V_3_0_PLUS
40
40
  from airflow.stats import Stats
41
41
  from airflow.utils.db import DBLocks, create_global_lock
42
42
  from airflow.utils.session import NEW_SESSION, provide_session
43
+ from airflow.utils.state import TaskInstanceState
43
44
 
44
45
  if TYPE_CHECKING:
45
46
  import argparse
@@ -68,7 +69,8 @@ class EdgeExecutor(BaseExecutor):
68
69
  """
69
70
  Check if already existing table matches the newest table schema.
70
71
 
71
- workaround till Airflow 3.0.0, then it is possible to use alembic also for provider distributions.
72
+ workaround till support for Airflow 2.x is dropped,
73
+ then it is possible to use alembic also for provider distributions.
72
74
  """
73
75
  inspector = inspect(engine)
74
76
  edge_job_columns = None
@@ -78,7 +80,7 @@ class EdgeExecutor(BaseExecutor):
78
80
  edge_job_columns = [column["name"] for column in edge_job_schema]
79
81
  for column in edge_job_schema:
80
82
  if column["name"] == "command":
81
- edge_job_command_len = column["type"].length
83
+ edge_job_command_len = column["type"].length # type: ignore[attr-defined]
82
84
 
83
85
  # version 0.6.0rc1 added new column concurrency_slots
84
86
  if edge_job_columns and "concurrency_slots" not in edge_job_columns:
@@ -284,7 +286,7 @@ class EdgeExecutor(BaseExecutor):
284
286
  map_index=job.map_index,
285
287
  session=session,
286
288
  )
287
- job.state = ti.state if ti else TaskInstanceState.REMOVED
289
+ job.state = ti.state if ti and ti.state else TaskInstanceState.REMOVED
288
290
 
289
291
  if job.state != TaskInstanceState.RUNNING:
290
292
  # Edge worker does not backport emitted Airflow metrics, so export some metrics
@@ -94,4 +94,4 @@ class EdgeJobModel(Base, LoggingMixin):
94
94
 
95
95
  @property
96
96
  def last_update_t(self) -> float:
97
- return self.last_update.timestamp()
97
+ return self.last_update.timestamp() if self.last_update else datetime.now().timestamp()
@@ -230,7 +230,9 @@ def request_maintenance(
230
230
  ) -> None:
231
231
  """Write maintenance request to the db."""
232
232
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
233
- worker: EdgeWorkerModel = session.scalar(query)
233
+ worker: EdgeWorkerModel | None = session.scalar(query)
234
+ if not worker:
235
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
234
236
  worker.state = EdgeWorkerState.MAINTENANCE_REQUEST
235
237
  worker.maintenance_comment = maintenance_comment
236
238
 
@@ -239,7 +241,9 @@ def request_maintenance(
239
241
  def exit_maintenance(worker_name: str, session: Session = NEW_SESSION) -> None:
240
242
  """Write maintenance exit to the db."""
241
243
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
242
- worker: EdgeWorkerModel = session.scalar(query)
244
+ worker: EdgeWorkerModel | None = session.scalar(query)
245
+ if not worker:
246
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
243
247
  worker.state = EdgeWorkerState.MAINTENANCE_EXIT
244
248
  worker.maintenance_comment = None
245
249
 
@@ -248,7 +252,9 @@ def exit_maintenance(worker_name: str, session: Session = NEW_SESSION) -> None:
248
252
  def remove_worker(worker_name: str, session: Session = NEW_SESSION) -> None:
249
253
  """Remove a worker that is offline or just gone from DB."""
250
254
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
251
- worker: EdgeWorkerModel = session.scalar(query)
255
+ worker: EdgeWorkerModel | None = session.scalar(query)
256
+ if not worker:
257
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
252
258
  if worker.state in (
253
259
  EdgeWorkerState.OFFLINE,
254
260
  EdgeWorkerState.OFFLINE_MAINTENANCE,
@@ -267,7 +273,9 @@ def change_maintenance_comment(
267
273
  ) -> None:
268
274
  """Write maintenance comment in the db."""
269
275
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
270
- worker: EdgeWorkerModel = session.scalar(query)
276
+ worker: EdgeWorkerModel | None = session.scalar(query)
277
+ if not worker:
278
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
271
279
  if worker.state in (
272
280
  EdgeWorkerState.MAINTENANCE_MODE,
273
281
  EdgeWorkerState.MAINTENANCE_PENDING,
@@ -285,7 +293,9 @@ def change_maintenance_comment(
285
293
  def request_shutdown(worker_name: str, session: Session = NEW_SESSION) -> None:
286
294
  """Request to shutdown the edge worker."""
287
295
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
288
- worker: EdgeWorkerModel = session.scalar(query)
296
+ worker: EdgeWorkerModel | None = session.scalar(query)
297
+ if not worker:
298
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
289
299
  if worker.state not in (
290
300
  EdgeWorkerState.OFFLINE,
291
301
  EdgeWorkerState.OFFLINE_MAINTENANCE,
@@ -298,7 +308,9 @@ def request_shutdown(worker_name: str, session: Session = NEW_SESSION) -> None:
298
308
  def add_worker_queues(worker_name: str, queues: list[str], session: Session = NEW_SESSION) -> None:
299
309
  """Add queues to an edge worker."""
300
310
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
301
- worker: EdgeWorkerModel = session.scalar(query)
311
+ worker: EdgeWorkerModel | None = session.scalar(query)
312
+ if not worker:
313
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
302
314
  if worker.state in (
303
315
  EdgeWorkerState.OFFLINE,
304
316
  EdgeWorkerState.OFFLINE_MAINTENANCE,
@@ -314,7 +326,9 @@ def add_worker_queues(worker_name: str, queues: list[str], session: Session = NE
314
326
  def remove_worker_queues(worker_name: str, queues: list[str], session: Session = NEW_SESSION) -> None:
315
327
  """Remove queues from an edge worker."""
316
328
  query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
317
- worker: EdgeWorkerModel = session.scalar(query)
329
+ worker: EdgeWorkerModel | None = session.scalar(query)
330
+ if not worker:
331
+ raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
318
332
  if worker.state in (
319
333
  EdgeWorkerState.OFFLINE,
320
334
  EdgeWorkerState.OFFLINE_MAINTENANCE,
@@ -66,8 +66,7 @@ else:
66
66
  from sqlalchemy import select
67
67
 
68
68
  from airflow.auth.managers.models.resource_details import AccessView
69
- from airflow.models.taskinstance import TaskInstanceState
70
- from airflow.utils.state import State
69
+ from airflow.utils.state import State, TaskInstanceState
71
70
  from airflow.utils.yaml import safe_load
72
71
  from airflow.www.auth import has_access_view
73
72
 
@@ -252,18 +251,10 @@ class EdgeExecutorPlugin(AirflowPlugin):
252
251
  fastapi_apps = [_get_api_endpoint()]
253
252
  react_apps = [
254
253
  {
255
- "name": "Edge Worker",
254
+ "name": "Edge Executor",
256
255
  "bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
257
256
  "destination": "nav",
258
- "url_route": "edge_worker",
259
- "category": "admin",
260
- "icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
261
- "icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
262
- },
263
- {
264
- "name": "Edge Worker Jobs",
265
- "bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
266
- "url_route": "edge_jobs",
257
+ "url_route": "edge_executor",
267
258
  "category": "admin",
268
259
  "icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
269
260
  "icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),