apache-airflow-providers-edge3 1.1.2rc2__tar.gz → 1.1.3__tar.gz

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 (81) hide show
  1. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/PKG-INFO +7 -7
  2. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/README.rst +3 -3
  3. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/changelog.rst +12 -0
  4. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/index.rst +1 -1
  5. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/provider.yaml +2 -1
  6. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/pyproject.toml +4 -4
  7. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/__init__.py +1 -1
  8. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/example_dags/win_test.py +1 -1
  9. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/executors/edge_executor.py +52 -12
  10. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/executors/test_edge_executor.py +97 -0
  11. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/architecture.rst +0 -0
  12. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/cli-ref.rst +0 -0
  13. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/commits.rst +0 -0
  14. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/conf.py +0 -0
  15. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/configurations-ref.rst +0 -0
  16. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/deployment.rst +0 -0
  17. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/edge_executor.rst +0 -0
  18. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/img/distributed_architecture.svg +0 -0
  19. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/img/edge_package.svg +0 -0
  20. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/img/worker_hosts.png +0 -0
  21. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/img/worker_maintenance.png +0 -0
  22. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/install_on_windows.rst +0 -0
  23. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/installing-providers-from-sources.rst +0 -0
  24. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/security.rst +0 -0
  25. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/ui_plugin.rst +0 -0
  26. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/docs/why_edge.rst +0 -0
  27. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/__init__.py +0 -0
  28. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/__init__.py +0 -0
  29. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/LICENSE +0 -0
  30. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/__init__.py +0 -0
  31. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/api_client.py +0 -0
  32. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/dataclasses.py +0 -0
  33. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/edge_command.py +0 -0
  34. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/signalling.py +0 -0
  35. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/cli/worker.py +0 -0
  36. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/example_dags/__init__.py +0 -0
  37. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/example_dags/integration_test.py +0 -0
  38. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/example_dags/win_notepad.py +0 -0
  39. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/executors/__init__.py +0 -0
  40. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/get_provider_info.py +0 -0
  41. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/models/__init__.py +0 -0
  42. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/models/edge_job.py +0 -0
  43. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/models/edge_logs.py +0 -0
  44. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/models/edge_worker.py +0 -0
  45. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/openapi/__init__.py +0 -0
  46. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/openapi/edge_worker_api_v1.yaml +0 -0
  47. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/plugins/__init__.py +0 -0
  48. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/plugins/edge_executor_plugin.py +0 -0
  49. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/plugins/templates/edge_worker_hosts.html +0 -0
  50. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/plugins/templates/edge_worker_jobs.html +0 -0
  51. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/version_compat.py +0 -0
  52. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/__init__.py +0 -0
  53. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/app.py +0 -0
  54. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/auth.py +0 -0
  55. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/datamodels.py +0 -0
  56. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/__init__.py +0 -0
  57. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/_v2_compat.py +0 -0
  58. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/_v2_routes.py +0 -0
  59. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/health.py +0 -0
  60. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/jobs.py +0 -0
  61. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/logs.py +0 -0
  62. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/src/airflow/providers/edge3/worker_api/routes/worker.py +0 -0
  63. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/conftest.py +0 -0
  64. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/__init__.py +0 -0
  65. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/__init__.py +0 -0
  66. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/__init__.py +0 -0
  67. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/test_api_client.py +0 -0
  68. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/test_dataclasses.py +0 -0
  69. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/test_edge_command.py +0 -0
  70. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/test_signalling.py +0 -0
  71. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/cli/test_worker.py +0 -0
  72. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/executors/__init__.py +0 -0
  73. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/models/__init__.py +0 -0
  74. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/plugins/__init__.py +0 -0
  75. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/plugins/test_edge_executor_plugin.py +0 -0
  76. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/__init__.py +0 -0
  77. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/routes/__init__.py +0 -0
  78. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/routes/test_health.py +0 -0
  79. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/routes/test_jobs.py +0 -0
  80. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/routes/test_logs.py +0 -0
  81. {apache_airflow_providers_edge3-1.1.2rc2 → apache_airflow_providers_edge3-1.1.3}/tests/unit/edge3/worker_api/routes/test_worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apache-airflow-providers-edge3
3
- Version: 1.1.2rc2
3
+ Version: 1.1.3
4
4
  Summary: Provider package apache-airflow-providers-edge3 for Apache Airflow
5
5
  Keywords: airflow-provider,edge3,airflow,integration
6
6
  Author-email: Apache Software Foundation <dev@airflow.apache.org>
@@ -20,12 +20,12 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: System :: Monitoring
23
- Requires-Dist: apache-airflow>=2.10.0rc1
23
+ Requires-Dist: apache-airflow>=2.10.0
24
24
  Requires-Dist: pydantic>=2.11.0
25
25
  Requires-Dist: retryhttp>=1.2.0,!=1.3.0
26
26
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
27
- Project-URL: Changelog, https://airflow.staged.apache.org/docs/apache-airflow-providers-edge3/1.1.2/changelog.html
28
- Project-URL: Documentation, https://airflow.staged.apache.org/docs/apache-airflow-providers-edge3/1.1.2
27
+ Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/changelog.html
28
+ Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3
29
29
  Project-URL: Mastodon, https://fosstodon.org/@airflow
30
30
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
31
31
  Project-URL: Source Code, https://github.com/apache/airflow
@@ -56,7 +56,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
56
56
 
57
57
  Package ``apache-airflow-providers-edge3``
58
58
 
59
- Release: ``1.1.2``
59
+ Release: ``1.1.3``
60
60
 
61
61
  Release Date: ``|PypiReleaseDate|``
62
62
 
@@ -82,7 +82,7 @@ This is a provider package for ``edge3`` provider. All classes for this provider
82
82
  are in ``airflow.providers.edge3`` python package.
83
83
 
84
84
  You can find package information and changelog for the provider
85
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.2/>`_.
85
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/>`_.
86
86
 
87
87
  Installation
88
88
  ------------
@@ -105,5 +105,5 @@ PIP package Version required
105
105
  ================== ===================
106
106
 
107
107
  The changelog for the provider package can be found in the
108
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.2/changelog.html>`_.
108
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/changelog.html>`_.
109
109
 
@@ -23,7 +23,7 @@
23
23
 
24
24
  Package ``apache-airflow-providers-edge3``
25
25
 
26
- Release: ``1.1.2``
26
+ Release: ``1.1.3``
27
27
 
28
28
  Release Date: ``|PypiReleaseDate|``
29
29
 
@@ -49,7 +49,7 @@ This is a provider package for ``edge3`` provider. All classes for this provider
49
49
  are in ``airflow.providers.edge3`` python package.
50
50
 
51
51
  You can find package information and changelog for the provider
52
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.2/>`_.
52
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/>`_.
53
53
 
54
54
  Installation
55
55
  ------------
@@ -72,4 +72,4 @@ PIP package Version required
72
72
  ================== ===================
73
73
 
74
74
  The changelog for the provider package can be found in the
75
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.2/changelog.html>`_.
75
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/changelog.html>`_.
@@ -27,6 +27,18 @@
27
27
  Changelog
28
28
  ---------
29
29
 
30
+ 1.1.3
31
+ .....
32
+
33
+ Bug Fixes
34
+ ~~~~~~~~~
35
+
36
+ * ``Fix: Prevent duplicate edge_job insertions for deferrable tasks in EdgeExecutor (#53610) (#53927)``
37
+
38
+ .. Below changes are excluded from the changelog. Move them to
39
+ appropriate section above if needed. Do not delete the lines(!):
40
+ * ``Remove parameter from Edge example (#53997)``
41
+
30
42
  1.1.2
31
43
  .....
32
44
 
@@ -90,7 +90,7 @@ Additional REST API endpoints are provided to distribute tasks and manage the ed
90
90
  are provided by the API server.
91
91
 
92
92
 
93
- Release: 1.1.2
93
+ Release: 1.1.3
94
94
 
95
95
  Release Date: ``|PypiReleaseDate|``
96
96
 
@@ -33,13 +33,14 @@ description: |
33
33
  are provided by the API server.
34
34
 
35
35
  state: ready
36
- source-date-epoch: 1751473321
36
+ source-date-epoch: 1754503142
37
37
 
38
38
  # Note that those versions are maintained by release manager - do not update them manually
39
39
  # with the exception of case where other provider in sources has >= new provider version.
40
40
  # In such case adding >= NEW_VERSION and bumping to NEW_VERSION in a provider have
41
41
  # to be done in the same PR
42
42
  versions:
43
+ - 1.1.3
43
44
  - 1.1.2
44
45
  - 1.1.1
45
46
  - 1.1.0
@@ -25,7 +25,7 @@ build-backend = "flit_core.buildapi"
25
25
 
26
26
  [project]
27
27
  name = "apache-airflow-providers-edge3"
28
- version = "1.1.2rc2"
28
+ version = "1.1.3"
29
29
  description = "Provider package apache-airflow-providers-edge3 for Apache Airflow"
30
30
  readme = "README.rst"
31
31
  authors = [
@@ -57,7 +57,7 @@ requires-python = ">=3.10"
57
57
  # Make sure to run ``breeze static-checks --type update-providers-dependencies --all-files``
58
58
  # After you modify the dependencies, and rebuild your Breeze CI image with ``breeze ci-image build``
59
59
  dependencies = [
60
- "apache-airflow>=2.10.0rc1",
60
+ "apache-airflow>=2.10.0",
61
61
  "pydantic>=2.11.0",
62
62
  "retryhttp>=1.2.0,!=1.3.0",
63
63
  ]
@@ -96,8 +96,8 @@ apache-airflow-providers-common-sql = {workspace = true}
96
96
  apache-airflow-providers-standard = {workspace = true}
97
97
 
98
98
  [project.urls]
99
- "Documentation" = "https://airflow.staged.apache.org/docs/apache-airflow-providers-edge3/1.1.2"
100
- "Changelog" = "https://airflow.staged.apache.org/docs/apache-airflow-providers-edge3/1.1.2/changelog.html"
99
+ "Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3"
100
+ "Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-edge3/1.1.3/changelog.html"
101
101
  "Bug Tracker" = "https://github.com/apache/airflow/issues"
102
102
  "Source Code" = "https://github.com/apache/airflow"
103
103
  "Slack Chat" = "https://s.apache.org/airflow-slack"
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "1.1.2"
32
+ __version__ = "1.1.3"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.10.0"
@@ -301,7 +301,7 @@ with DAG(
301
301
  except AirflowNotFoundException:
302
302
  print("Connection 'integration_test' not found... but also OK.")
303
303
 
304
- command = CmdOperator(task_id="command", command="echo Parameter is {{params.mapping_count}}")
304
+ command = CmdOperator(task_id="command", command="echo Hello World")
305
305
 
306
306
  def python_call():
307
307
  print("Hello world")
@@ -140,20 +140,40 @@ class EdgeExecutor(BaseExecutor):
140
140
  del self.edge_queued_tasks[key]
141
141
 
142
142
  self.validate_airflow_tasks_run_command(command) # type: ignore[attr-defined]
143
- session.add(
144
- EdgeJobModel(
143
+
144
+ # Check if job already exists with same dag_id, task_id, run_id, map_index, try_number
145
+ existing_job = (
146
+ session.query(EdgeJobModel)
147
+ .filter_by(
145
148
  dag_id=key.dag_id,
146
149
  task_id=key.task_id,
147
150
  run_id=key.run_id,
148
151
  map_index=key.map_index,
149
152
  try_number=key.try_number,
150
- state=TaskInstanceState.QUEUED,
151
- queue=queue or DEFAULT_QUEUE,
152
- concurrency_slots=task_instance.pool_slots,
153
- command=str(command),
154
153
  )
154
+ .first()
155
155
  )
156
156
 
157
+ if existing_job:
158
+ existing_job.state = TaskInstanceState.QUEUED
159
+ existing_job.queue = queue or DEFAULT_QUEUE
160
+ existing_job.concurrency_slots = task_instance.pool_slots
161
+ existing_job.command = str(command)
162
+ else:
163
+ session.add(
164
+ EdgeJobModel(
165
+ dag_id=key.dag_id,
166
+ task_id=key.task_id,
167
+ run_id=key.run_id,
168
+ map_index=key.map_index,
169
+ try_number=key.try_number,
170
+ state=TaskInstanceState.QUEUED,
171
+ queue=queue or DEFAULT_QUEUE,
172
+ concurrency_slots=task_instance.pool_slots,
173
+ command=str(command),
174
+ )
175
+ )
176
+
157
177
  @provide_session
158
178
  def queue_workload(
159
179
  self,
@@ -168,20 +188,40 @@ class EdgeExecutor(BaseExecutor):
168
188
 
169
189
  task_instance = workload.ti
170
190
  key = task_instance.key
171
- session.add(
172
- EdgeJobModel(
191
+
192
+ # Check if job already exists with same dag_id, task_id, run_id, map_index, try_number
193
+ existing_job = (
194
+ session.query(EdgeJobModel)
195
+ .filter_by(
173
196
  dag_id=key.dag_id,
174
197
  task_id=key.task_id,
175
198
  run_id=key.run_id,
176
199
  map_index=key.map_index,
177
200
  try_number=key.try_number,
178
- state=TaskInstanceState.QUEUED,
179
- queue=task_instance.queue,
180
- concurrency_slots=task_instance.pool_slots,
181
- command=workload.model_dump_json(),
182
201
  )
202
+ .first()
183
203
  )
184
204
 
205
+ if existing_job:
206
+ existing_job.state = TaskInstanceState.QUEUED
207
+ existing_job.queue = task_instance.queue
208
+ existing_job.concurrency_slots = task_instance.pool_slots
209
+ existing_job.command = workload.model_dump_json()
210
+ else:
211
+ session.add(
212
+ EdgeJobModel(
213
+ dag_id=key.dag_id,
214
+ task_id=key.task_id,
215
+ run_id=key.run_id,
216
+ map_index=key.map_index,
217
+ try_number=key.try_number,
218
+ state=TaskInstanceState.QUEUED,
219
+ queue=task_instance.queue,
220
+ concurrency_slots=task_instance.pool_slots,
221
+ command=workload.model_dump_json(),
222
+ )
223
+ )
224
+
185
225
  def _check_worker_liveness(self, session: Session) -> bool:
186
226
  """Reset worker state if heartbeat timed out."""
187
227
  changed = False
@@ -247,6 +247,11 @@ class TestEdgeExecutor:
247
247
 
248
248
  # Prepare some data
249
249
  with create_session() as session:
250
+ # Clear existing workers to avoid unique constraint violation
251
+ session.query(EdgeWorkerModel).delete()
252
+ session.commit()
253
+
254
+ # Add workers with different states
250
255
  for worker_name, state, last_heartbeat in [
251
256
  (
252
257
  "inactive_timed_out_worker",
@@ -338,3 +343,95 @@ class TestEdgeExecutor:
338
343
  with create_session() as session:
339
344
  jobs = session.query(EdgeJobModel).all()
340
345
  assert len(jobs) == 1
346
+
347
+ @pytest.mark.skipif(AIRFLOW_V_3_0_PLUS, reason="API only available in Airflow <3.0")
348
+ def test_execute_async_updates_existing_job(self):
349
+ executor, key = self.get_test_executor()
350
+
351
+ # First insert a job with the same key
352
+ with create_session() as session:
353
+ session.add(
354
+ EdgeJobModel(
355
+ dag_id=key.dag_id,
356
+ run_id=key.run_id,
357
+ task_id=key.task_id,
358
+ map_index=key.map_index,
359
+ try_number=key.try_number,
360
+ state=TaskInstanceState.SCHEDULED,
361
+ queue="default",
362
+ concurrency_slots=1,
363
+ command="old-command",
364
+ last_update=timezone.utcnow(),
365
+ )
366
+ )
367
+ session.commit()
368
+
369
+ # Trigger execute_async which should update the existing job
370
+ executor.edge_queued_tasks = deepcopy(executor.queued_tasks)
371
+ executor.execute_async(key=key, command=["airflow", "tasks", "run", "new", "command"])
372
+
373
+ with create_session() as session:
374
+ jobs = session.query(EdgeJobModel).all()
375
+ assert len(jobs) == 1
376
+ job = jobs[0]
377
+ assert job.state == TaskInstanceState.QUEUED
378
+ assert job.command != "old-command"
379
+ assert "new" in job.command
380
+
381
+ @pytest.mark.skipif(not AIRFLOW_V_3_0_PLUS, reason="API only available in Airflow 3.0+")
382
+ def test_queue_workload_updates_existing_job(self):
383
+ from uuid import uuid4
384
+
385
+ from airflow.executors.workloads import ExecuteTask, TaskInstance
386
+
387
+ executor = self.get_test_executor()[0]
388
+
389
+ key = TaskInstanceKey(dag_id="mock", run_id="mock", task_id="mock", map_index=-1, try_number=1)
390
+
391
+ # Insert an existing job
392
+ with create_session() as session:
393
+ session.add(
394
+ EdgeJobModel(
395
+ dag_id=key.dag_id,
396
+ task_id=key.task_id,
397
+ run_id=key.run_id,
398
+ map_index=key.map_index,
399
+ try_number=key.try_number,
400
+ state=TaskInstanceState.SCHEDULED,
401
+ queue="default",
402
+ command="old-command",
403
+ concurrency_slots=1,
404
+ last_update=timezone.utcnow(),
405
+ )
406
+ )
407
+ session.commit()
408
+
409
+ # Queue a workload with same key
410
+ workload = ExecuteTask(
411
+ token="mock",
412
+ ti=TaskInstance(
413
+ id=uuid4(),
414
+ task_id=key.task_id,
415
+ dag_id=key.dag_id,
416
+ run_id=key.run_id,
417
+ try_number=key.try_number,
418
+ map_index=key.map_index,
419
+ pool_slots=1,
420
+ queue="updated-queue",
421
+ priority_weight=1,
422
+ start_date=timezone.utcnow(),
423
+ dag_version_id=uuid4(),
424
+ ),
425
+ dag_rel_path="mock.py",
426
+ log_path="mock.log",
427
+ bundle_info={"name": "n/a", "version": "no matter"},
428
+ )
429
+
430
+ executor.queue_workload(workload=workload)
431
+
432
+ with create_session() as session:
433
+ jobs = session.query(EdgeJobModel).all()
434
+ assert len(jobs) == 1
435
+ job = jobs[0]
436
+ assert job.queue == "updated-queue"
437
+ assert job.command != "old-command"