This is an automated email from the ASF dual-hosted git repository.
henry3260 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new fb512efe2ac Add asset state store commands to airflowctl (#70771)
fb512efe2ac is described below
commit fb512efe2ac7cee7ecd861e392ad310e7844d990
Author: Justin Pakzad <[email protected]>
AuthorDate: Mon Sep 7 13:17:44 2026 -0400
Add asset state store commands to airflowctl (#70771)
---
.../airflowctl_tests/test_airflowctl_commands.py | 6 +
airflow-ctl/docs/images/command_hashes.txt | 2 +-
airflow-ctl/docs/images/output_assets.svg | 166 ++++++++++++---------
airflow-ctl/src/airflowctl/api/operations.py | 36 +++++
airflow-ctl/src/airflowctl/ctl/cli_config.py | 1 +
airflow-ctl/src/airflowctl/ctl/help_texts.yaml | 5 +
.../tests/airflow_ctl/api/test_operations.py | 75 +++++++++-
7 files changed, 222 insertions(+), 69 deletions(-)
diff --git
a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
index 14a500c4d5b..7e5c94014f3 100644
--- a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
+++ b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
@@ -56,6 +56,12 @@ TEST_COMMANDS = [
"assets list",
"assets get 1",
"assets create-event --asset-id=1",
+ "assets set-state-store 1 test_key test_value",
+ "assets get-state-store 1 test_key",
+ "assets list-state-store 1",
+ "assets delete-state-store 1 test_key",
+ "assets set-state-store 1 clear_key value",
+ "assets clear-state-store 1",
# Backfill commands
"backfill list example_bash_operator",
# Config commands
diff --git a/airflow-ctl/docs/images/command_hashes.txt
b/airflow-ctl/docs/images/command_hashes.txt
index 4e48ad37fee..b8994fb977b 100644
--- a/airflow-ctl/docs/images/command_hashes.txt
+++ b/airflow-ctl/docs/images/command_hashes.txt
@@ -1,5 +1,5 @@
main:2c358f5fc894541cab11854dcd67658a
-assets:f225579fbecb3d919d152b4f1ba83185
+assets:fa2bceb5343af0d3cda2a1594d95787e
auth:d79e9c7d00c432bdbcbc2a86e2e32053
backfill:74c8737b0a62a86ed3605fa9e6165874
config:a3d936cb15fe3b547bf6c82cf93d923f
diff --git a/airflow-ctl/docs/images/output_assets.svg
b/airflow-ctl/docs/images/output_assets.svg
index c5dc475c84a..b07731e2f8c 100644
--- a/airflow-ctl/docs/images/output_assets.svg
+++ b/airflow-ctl/docs/images/output_assets.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 933 733.1999999999999"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 933 928.4"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -19,150 +19,182 @@
font-weight: 700;
}
- .terminal-2396817043-matrix {
+ .terminal-4122115999-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-2396817043-title {
+ .terminal-4122115999-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-2396817043-r1 { fill: #ff8700 }
-.terminal-2396817043-r2 { fill: #c5c8c6 }
-.terminal-2396817043-r3 { fill: #808080 }
-.terminal-2396817043-r4 { fill: #68a0b3 }
+ .terminal-4122115999-r1 { fill: #ff8700 }
+.terminal-4122115999-r2 { fill: #c5c8c6 }
+.terminal-4122115999-r3 { fill: #808080 }
+.terminal-4122115999-r4 { fill: #68a0b3 }
</style>
<defs>
- <clipPath id="terminal-2396817043-clip-terminal">
- <rect x="0" y="0" width="914.0" height="682.1999999999999" />
+ <clipPath id="terminal-4122115999-clip-terminal">
+ <rect x="0" y="0" width="914.0" height="877.4" />
</clipPath>
- <clipPath id="terminal-2396817043-line-0">
+ <clipPath id="terminal-4122115999-line-0">
<rect x="0" y="1.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-1">
+<clipPath id="terminal-4122115999-line-1">
<rect x="0" y="25.9" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-2">
+<clipPath id="terminal-4122115999-line-2">
<rect x="0" y="50.3" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-3">
+<clipPath id="terminal-4122115999-line-3">
<rect x="0" y="74.7" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-4">
+<clipPath id="terminal-4122115999-line-4">
<rect x="0" y="99.1" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-5">
+<clipPath id="terminal-4122115999-line-5">
<rect x="0" y="123.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-6">
+<clipPath id="terminal-4122115999-line-6">
<rect x="0" y="147.9" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-7">
+<clipPath id="terminal-4122115999-line-7">
<rect x="0" y="172.3" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-8">
+<clipPath id="terminal-4122115999-line-8">
<rect x="0" y="196.7" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-9">
+<clipPath id="terminal-4122115999-line-9">
<rect x="0" y="221.1" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-10">
+<clipPath id="terminal-4122115999-line-10">
<rect x="0" y="245.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-11">
+<clipPath id="terminal-4122115999-line-11">
<rect x="0" y="269.9" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-12">
+<clipPath id="terminal-4122115999-line-12">
<rect x="0" y="294.3" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-13">
+<clipPath id="terminal-4122115999-line-13">
<rect x="0" y="318.7" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-14">
+<clipPath id="terminal-4122115999-line-14">
<rect x="0" y="343.1" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-15">
+<clipPath id="terminal-4122115999-line-15">
<rect x="0" y="367.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-16">
+<clipPath id="terminal-4122115999-line-16">
<rect x="0" y="391.9" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-17">
+<clipPath id="terminal-4122115999-line-17">
<rect x="0" y="416.3" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-18">
+<clipPath id="terminal-4122115999-line-18">
<rect x="0" y="440.7" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-19">
+<clipPath id="terminal-4122115999-line-19">
<rect x="0" y="465.1" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-20">
+<clipPath id="terminal-4122115999-line-20">
<rect x="0" y="489.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-21">
+<clipPath id="terminal-4122115999-line-21">
<rect x="0" y="513.9" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-22">
+<clipPath id="terminal-4122115999-line-22">
<rect x="0" y="538.3" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-23">
+<clipPath id="terminal-4122115999-line-23">
<rect x="0" y="562.7" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-24">
+<clipPath id="terminal-4122115999-line-24">
<rect x="0" y="587.1" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-25">
+<clipPath id="terminal-4122115999-line-25">
<rect x="0" y="611.5" width="915" height="24.65"/>
</clipPath>
-<clipPath id="terminal-2396817043-line-26">
+<clipPath id="terminal-4122115999-line-26">
<rect x="0" y="635.9" width="915" height="24.65"/>
</clipPath>
+<clipPath id="terminal-4122115999-line-27">
+ <rect x="0" y="660.3" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-28">
+ <rect x="0" y="684.7" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-29">
+ <rect x="0" y="709.1" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-30">
+ <rect x="0" y="733.5" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-31">
+ <rect x="0" y="757.9" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-32">
+ <rect x="0" y="782.3" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-33">
+ <rect x="0" y="806.7" width="915" height="24.65"/>
+ </clipPath>
+<clipPath id="terminal-4122115999-line-34">
+ <rect x="0" y="831.1" width="915" height="24.65"/>
+ </clipPath>
</defs>
- <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="931" height="731.2" rx="8"/>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="931" height="926.4" rx="8"/>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
- <g transform="translate(9, 41)"
clip-path="url(#terminal-2396817043-clip-terminal)">
+ <g transform="translate(9, 41)"
clip-path="url(#terminal-4122115999-clip-terminal)">
- <g class="terminal-2396817043-matrix">
- <text class="terminal-2396817043-r1" x="0" y="20" textLength="73.2"
clip-path="url(#terminal-2396817043-line-0)">Usage:</text><text
class="terminal-2396817043-r3" x="85.4" y="20" textLength="207.4"
clip-path="url(#terminal-2396817043-line-0)">airflowctl assets</text><text
class="terminal-2396817043-r2" x="292.8" y="20" textLength="24.4"
clip-path="url(#terminal-2396817043-line-0)"> [</text><text
class="terminal-2396817043-r4" x="317.2" y="20" textLength="24.4" clip-path="ur
[...]
-</text><text class="terminal-2396817043-r2" x="915" y="44.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-1)">
-</text><text class="terminal-2396817043-r2" x="0" y="68.8" textLength="305"
clip-path="url(#terminal-2396817043-line-2)">Perform Assets operations</text><text
class="terminal-2396817043-r2" x="915" y="68.8" textLength="12.2"
clip-path="url(#terminal-2396817043-line-2)">
-</text><text class="terminal-2396817043-r2" x="915" y="93.2" textLength="12.2"
clip-path="url(#terminal-2396817043-line-3)">
-</text><text class="terminal-2396817043-r1" x="0" y="117.6" textLength="256.2"
clip-path="url(#terminal-2396817043-line-4)">Positional Arguments:</text><text
class="terminal-2396817043-r2" x="915" y="117.6" textLength="12.2"
clip-path="url(#terminal-2396817043-line-4)">
-</text><text class="terminal-2396817043-r4" x="24.4" y="142" textLength="85.4"
clip-path="url(#terminal-2396817043-line-5)">COMMAND</text><text
class="terminal-2396817043-r2" x="915" y="142" textLength="12.2"
clip-path="url(#terminal-2396817043-line-5)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="166.4"
textLength="146.4"
clip-path="url(#terminal-2396817043-line-6)">create-event</text><text
class="terminal-2396817043-r2" x="292.8" y="166.4" textLength="402.6"
clip-path="url(#terminal-2396817043-line-6)">Create an event for a given asset</text><text
class="terminal-2396817043-r2" x="915" y="166.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-6)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="190.8"
textLength="292.8"
clip-path="url(#terminal-2396817043-line-7)">delete-dag-queued-events</text><text
class="terminal-2396817043-r2" x="915" y="190.8" textLength="12.2"
clip-path="url(#terminal-2396817043-line-7)">
-</text><text class="terminal-2396817043-r2" x="292.8" y="215.2"
textLength="561.2"
clip-path="url(#terminal-2396817043-line-8)">Delete all queued asset events for a given Dag</text><text
class="terminal-2396817043-r2" x="915" y="215.2" textLength="12.2"
clip-path="url(#terminal-2396817043-line-8)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="239.6"
textLength="231.8"
clip-path="url(#terminal-2396817043-line-9)">delete-queued-event</text><text
class="terminal-2396817043-r2" x="915" y="239.6" textLength="12.2"
clip-path="url(#terminal-2396817043-line-9)">
-</text><text class="terminal-2396817043-r2" x="292.8" y="264"
textLength="597.8"
clip-path="url(#terminal-2396817043-line-10)">Delete a specific queued asset event for a given </text><text
class="terminal-2396817043-r2" x="915" y="264" textLength="12.2"
clip-path="url(#terminal-2396817043-line-10)">
-</text><text class="terminal-2396817043-r2" x="0" y="288.4" textLength="158.6"
clip-path="url(#terminal-2396817043-line-11)">Dag and asset</text><text
class="terminal-2396817043-r2" x="915" y="288.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-11)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="312.8"
textLength="244"
clip-path="url(#terminal-2396817043-line-12)">delete-queued-events</text><text
class="terminal-2396817043-r2" x="915" y="312.8" textLength="12.2"
clip-path="url(#terminal-2396817043-line-12)">
-</text><text class="terminal-2396817043-r2" x="292.8" y="337.2"
textLength="512.4"
clip-path="url(#terminal-2396817043-line-13)">Delete all queued events for a given asset</text><text
class="terminal-2396817043-r2" x="915" y="337.2" textLength="12.2"
clip-path="url(#terminal-2396817043-line-13)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="361.6"
textLength="36.6" clip-path="url(#terminal-2396817043-line-14)">get</text><text
class="terminal-2396817043-r2" x="292.8" y="361.6" textLength="329.4"
clip-path="url(#terminal-2396817043-line-14)">Retrieve an asset by its ID</text><text
class="terminal-2396817043-r2" x="915" y="361.6" textLength="12.2"
clip-path="url(#terminal-2396817043-line-14)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="386"
textLength="109.8"
clip-path="url(#terminal-2396817043-line-15)">get-alias</text><text
class="terminal-2396817043-r2" x="292.8" y="386" textLength="402.6"
clip-path="url(#terminal-2396817043-line-15)">Retrieve an asset alias by its ID</text><text
class="terminal-2396817043-r2" x="915" y="386" textLength="12.2"
clip-path="url(#terminal-2396817043-line-15)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="410.4"
textLength="244"
clip-path="url(#terminal-2396817043-line-16)">get-dag-queued-event</text><text
class="terminal-2396817043-r2" x="915" y="410.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-16)">
-</text><text class="terminal-2396817043-r2" x="292.8" y="434.8"
textLength="622.2"
clip-path="url(#terminal-2396817043-line-17)">Retrieve a specific queued asset event for a given </text><text
class="terminal-2396817043-r2" x="915" y="434.8" textLength="12.2"
clip-path="url(#terminal-2396817043-line-17)">
-</text><text class="terminal-2396817043-r2" x="0" y="459.2" textLength="158.6"
clip-path="url(#terminal-2396817043-line-18)">Dag and asset</text><text
class="terminal-2396817043-r2" x="915" y="459.2" textLength="12.2"
clip-path="url(#terminal-2396817043-line-18)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="483.6"
textLength="256.2"
clip-path="url(#terminal-2396817043-line-19)">get-dag-queued-events</text><text
class="terminal-2396817043-r2" x="915" y="483.6" textLength="12.2"
clip-path="url(#terminal-2396817043-line-19)">
-</text><text class="terminal-2396817043-r2" x="292.8" y="508" textLength="488"
clip-path="url(#terminal-2396817043-line-20)">List queued asset events for a given Dag</text><text
class="terminal-2396817043-r2" x="915" y="508" textLength="12.2"
clip-path="url(#terminal-2396817043-line-20)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="532.4"
textLength="207.4"
clip-path="url(#terminal-2396817043-line-21)">get-queued-events</text><text
class="terminal-2396817043-r2" x="292.8" y="532.4" textLength="439.2"
clip-path="url(#terminal-2396817043-line-21)">List queued events for a given asset</text><text
class="terminal-2396817043-r2" x="915" y="532.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-21)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="556.8"
textLength="48.8"
clip-path="url(#terminal-2396817043-line-22)">list</text><text
class="terminal-2396817043-r2" x="292.8" y="556.8" textLength="183"
clip-path="url(#terminal-2396817043-line-22)">List all assets</text><text
class="terminal-2396817043-r2" x="915" y="556.8" textLength="12.2"
clip-path="url(#terminal-2396817043-line-22)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="581.2"
textLength="146.4"
clip-path="url(#terminal-2396817043-line-23)">list-aliases</text><text
class="terminal-2396817043-r2" x="292.8" y="581.2" textLength="268.4"
clip-path="url(#terminal-2396817043-line-23)">List all asset aliases</text><text
class="terminal-2396817043-r2" x="915" y="581.2" textLength="12.2"
clip-path="url(#terminal-2396817043-line-23)">
-</text><text class="terminal-2396817043-r4" x="48.8" y="605.6"
textLength="134.2"
clip-path="url(#terminal-2396817043-line-24)">materialize</text><text
class="terminal-2396817043-r2" x="292.8" y="605.6" textLength="549"
clip-path="url(#terminal-2396817043-line-24)">Trigger materialization of an asset by its ID</text><text
class="terminal-2396817043-r2" x="915" y="605.6" textLength="12.2"
clip-path="url(#terminal-2396817043-line-24)">
-</text><text class="terminal-2396817043-r2" x="915" y="630" textLength="12.2"
clip-path="url(#terminal-2396817043-line-25)">
-</text><text class="terminal-2396817043-r1" x="0" y="654.4" textLength="97.6"
clip-path="url(#terminal-2396817043-line-26)">Options:</text><text
class="terminal-2396817043-r2" x="915" y="654.4" textLength="12.2"
clip-path="url(#terminal-2396817043-line-26)">
-</text><text class="terminal-2396817043-r4" x="24.4" y="678.8"
textLength="24.4" clip-path="url(#terminal-2396817043-line-27)">-h</text><text
class="terminal-2396817043-r2" x="48.8" y="678.8" textLength="24.4"
clip-path="url(#terminal-2396817043-line-27)">, </text><text
class="terminal-2396817043-r4" x="73.2" y="678.8" textLength="73.2"
clip-path="url(#terminal-2396817043-line-27)">--help</text><text
class="terminal-2396817043-r2" x="292.8" y="678.8" textLength="378.2"
clip-path="ur [...]
+ <g class="terminal-4122115999-matrix">
+ <text class="terminal-4122115999-r1" x="0" y="20" textLength="73.2"
clip-path="url(#terminal-4122115999-line-0)">Usage:</text><text
class="terminal-4122115999-r3" x="85.4" y="20" textLength="207.4"
clip-path="url(#terminal-4122115999-line-0)">airflowctl assets</text><text
class="terminal-4122115999-r2" x="292.8" y="20" textLength="24.4"
clip-path="url(#terminal-4122115999-line-0)"> [</text><text
class="terminal-4122115999-r4" x="317.2" y="20" textLength="24.4" clip-path="ur
[...]
+</text><text class="terminal-4122115999-r2" x="915" y="44.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-1)">
+</text><text class="terminal-4122115999-r2" x="0" y="68.8" textLength="305"
clip-path="url(#terminal-4122115999-line-2)">Perform Assets operations</text><text
class="terminal-4122115999-r2" x="915" y="68.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-2)">
+</text><text class="terminal-4122115999-r2" x="915" y="93.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-3)">
+</text><text class="terminal-4122115999-r1" x="0" y="117.6" textLength="256.2"
clip-path="url(#terminal-4122115999-line-4)">Positional Arguments:</text><text
class="terminal-4122115999-r2" x="915" y="117.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-4)">
+</text><text class="terminal-4122115999-r4" x="24.4" y="142" textLength="85.4"
clip-path="url(#terminal-4122115999-line-5)">COMMAND</text><text
class="terminal-4122115999-r2" x="915" y="142" textLength="12.2"
clip-path="url(#terminal-4122115999-line-5)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="166.4"
textLength="207.4"
clip-path="url(#terminal-4122115999-line-6)">clear-state-store</text><text
class="terminal-4122115999-r2" x="292.8" y="166.4" textLength="610"
clip-path="url(#terminal-4122115999-line-6)">Delete all state store keys for an asset by its ID</text><text
class="terminal-4122115999-r2" x="915" y="166.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-6)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="190.8"
textLength="146.4"
clip-path="url(#terminal-4122115999-line-7)">create-event</text><text
class="terminal-4122115999-r2" x="292.8" y="190.8" textLength="402.6"
clip-path="url(#terminal-4122115999-line-7)">Create an event for a given asset</text><text
class="terminal-4122115999-r2" x="915" y="190.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-7)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="215.2"
textLength="292.8"
clip-path="url(#terminal-4122115999-line-8)">delete-dag-queued-events</text><text
class="terminal-4122115999-r2" x="915" y="215.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-8)">
+</text><text class="terminal-4122115999-r2" x="292.8" y="239.6"
textLength="561.2"
clip-path="url(#terminal-4122115999-line-9)">Delete all queued asset events for a given Dag</text><text
class="terminal-4122115999-r2" x="915" y="239.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-9)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="264"
textLength="231.8"
clip-path="url(#terminal-4122115999-line-10)">delete-queued-event</text><text
class="terminal-4122115999-r2" x="915" y="264" textLength="12.2"
clip-path="url(#terminal-4122115999-line-10)">
+</text><text class="terminal-4122115999-r2" x="292.8" y="288.4"
textLength="597.8"
clip-path="url(#terminal-4122115999-line-11)">Delete a specific queued asset event for a given </text><text
class="terminal-4122115999-r2" x="915" y="288.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-11)">
+</text><text class="terminal-4122115999-r2" x="0" y="312.8" textLength="158.6"
clip-path="url(#terminal-4122115999-line-12)">Dag and asset</text><text
class="terminal-4122115999-r2" x="915" y="312.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-12)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="337.2"
textLength="244"
clip-path="url(#terminal-4122115999-line-13)">delete-queued-events</text><text
class="terminal-4122115999-r2" x="915" y="337.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-13)">
+</text><text class="terminal-4122115999-r2" x="292.8" y="361.6"
textLength="512.4"
clip-path="url(#terminal-4122115999-line-14)">Delete all queued events for a given asset</text><text
class="terminal-4122115999-r2" x="915" y="361.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-14)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="386"
textLength="219.6"
clip-path="url(#terminal-4122115999-line-15)">delete-state-store</text><text
class="terminal-4122115999-r2" x="292.8" y="386" textLength="622.2"
clip-path="url(#terminal-4122115999-line-15)">Delete a single asset state store key by its ID and</text><text
class="terminal-4122115999-r2" x="915" y="386" textLength="12.2"
clip-path="url(#terminal-4122115999-line-15)">
+</text><text class="terminal-4122115999-r2" x="0" y="410.4" textLength="36.6"
clip-path="url(#terminal-4122115999-line-16)">key</text><text
class="terminal-4122115999-r2" x="915" y="410.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-16)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="434.8"
textLength="36.6" clip-path="url(#terminal-4122115999-line-17)">get</text><text
class="terminal-4122115999-r2" x="292.8" y="434.8" textLength="329.4"
clip-path="url(#terminal-4122115999-line-17)">Retrieve an asset by its ID</text><text
class="terminal-4122115999-r2" x="915" y="434.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-17)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="459.2"
textLength="109.8"
clip-path="url(#terminal-4122115999-line-18)">get-alias</text><text
class="terminal-4122115999-r2" x="292.8" y="459.2" textLength="402.6"
clip-path="url(#terminal-4122115999-line-18)">Retrieve an asset alias by its ID</text><text
class="terminal-4122115999-r2" x="915" y="459.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-18)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="483.6"
textLength="244"
clip-path="url(#terminal-4122115999-line-19)">get-dag-queued-event</text><text
class="terminal-4122115999-r2" x="915" y="483.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-19)">
+</text><text class="terminal-4122115999-r2" x="292.8" y="508"
textLength="622.2"
clip-path="url(#terminal-4122115999-line-20)">Retrieve a specific queued asset event for a given </text><text
class="terminal-4122115999-r2" x="915" y="508" textLength="12.2"
clip-path="url(#terminal-4122115999-line-20)">
+</text><text class="terminal-4122115999-r2" x="0" y="532.4" textLength="158.6"
clip-path="url(#terminal-4122115999-line-21)">Dag and asset</text><text
class="terminal-4122115999-r2" x="915" y="532.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-21)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="556.8"
textLength="256.2"
clip-path="url(#terminal-4122115999-line-22)">get-dag-queued-events</text><text
class="terminal-4122115999-r2" x="915" y="556.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-22)">
+</text><text class="terminal-4122115999-r2" x="292.8" y="581.2"
textLength="488"
clip-path="url(#terminal-4122115999-line-23)">List queued asset events for a given Dag</text><text
class="terminal-4122115999-r2" x="915" y="581.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-23)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="605.6"
textLength="207.4"
clip-path="url(#terminal-4122115999-line-24)">get-queued-events</text><text
class="terminal-4122115999-r2" x="292.8" y="605.6" textLength="439.2"
clip-path="url(#terminal-4122115999-line-24)">List queued events for a given asset</text><text
class="terminal-4122115999-r2" x="915" y="605.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-24)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="630" textLength="183"
clip-path="url(#terminal-4122115999-line-25)">get-state-store</text><text
class="terminal-4122115999-r2" x="292.8" y="630" textLength="622.2"
clip-path="url(#terminal-4122115999-line-25)">Retrieve a single asset state store entry by its ID</text><text
class="terminal-4122115999-r2" x="915" y="630" textLength="12.2"
clip-path="url(#terminal-4122115999-line-25)">
+</text><text class="terminal-4122115999-r2" x="0" y="654.4" textLength="85.4"
clip-path="url(#terminal-4122115999-line-26)">and key</text><text
class="terminal-4122115999-r2" x="915" y="654.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-26)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="678.8"
textLength="48.8"
clip-path="url(#terminal-4122115999-line-27)">list</text><text
class="terminal-4122115999-r2" x="292.8" y="678.8" textLength="183"
clip-path="url(#terminal-4122115999-line-27)">List all assets</text><text
class="terminal-4122115999-r2" x="915" y="678.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-27)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="703.2"
textLength="146.4"
clip-path="url(#terminal-4122115999-line-28)">list-aliases</text><text
class="terminal-4122115999-r2" x="292.8" y="703.2" textLength="268.4"
clip-path="url(#terminal-4122115999-line-28)">List all asset aliases</text><text
class="terminal-4122115999-r2" x="915" y="703.2" textLength="12.2"
clip-path="url(#terminal-4122115999-line-28)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="727.6"
textLength="195.2"
clip-path="url(#terminal-4122115999-line-29)">list-state-store</text><text
class="terminal-4122115999-r2" x="292.8" y="727.6" textLength="500.2"
clip-path="url(#terminal-4122115999-line-29)">List all state store entries for an asset</text><text
class="terminal-4122115999-r2" x="915" y="727.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-29)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="752"
textLength="134.2"
clip-path="url(#terminal-4122115999-line-30)">materialize</text><text
class="terminal-4122115999-r2" x="292.8" y="752" textLength="549"
clip-path="url(#terminal-4122115999-line-30)">Trigger materialization of an asset by its ID</text><text
class="terminal-4122115999-r2" x="915" y="752" textLength="12.2"
clip-path="url(#terminal-4122115999-line-30)">
+</text><text class="terminal-4122115999-r4" x="48.8" y="776.4"
textLength="183"
clip-path="url(#terminal-4122115999-line-31)">set-state-store</text><text
class="terminal-4122115999-r2" x="292.8" y="776.4" textLength="610"
clip-path="url(#terminal-4122115999-line-31)">Set an asset state store value. Parsed as JSON if </text><text
class="terminal-4122115999-r2" x="915" y="776.4" textLength="12.2"
clip-path="url(#terminal-4122115999-line-31)">
+</text><text class="terminal-4122115999-r2" x="0" y="800.8" textLength="427"
clip-path="url(#terminal-4122115999-line-32)">valid, otherwise stored as a string</text><text
class="terminal-4122115999-r2" x="915" y="800.8" textLength="12.2"
clip-path="url(#terminal-4122115999-line-32)">
+</text><text class="terminal-4122115999-r2" x="915" y="825.2"
textLength="12.2" clip-path="url(#terminal-4122115999-line-33)">
+</text><text class="terminal-4122115999-r1" x="0" y="849.6" textLength="97.6"
clip-path="url(#terminal-4122115999-line-34)">Options:</text><text
class="terminal-4122115999-r2" x="915" y="849.6" textLength="12.2"
clip-path="url(#terminal-4122115999-line-34)">
+</text><text class="terminal-4122115999-r4" x="24.4" y="874" textLength="24.4"
clip-path="url(#terminal-4122115999-line-35)">-h</text><text
class="terminal-4122115999-r2" x="48.8" y="874" textLength="24.4"
clip-path="url(#terminal-4122115999-line-35)">, </text><text
class="terminal-4122115999-r4" x="73.2" y="874" textLength="73.2"
clip-path="url(#terminal-4122115999-line-35)">--help</text><text
class="terminal-4122115999-r2" x="292.8" y="874" textLength="378.2"
clip-path="url(#termi [...]
</text>
</g>
</g>
diff --git a/airflow-ctl/src/airflowctl/api/operations.py
b/airflow-ctl/src/airflowctl/api/operations.py
index 4916203247a..7c10eebedf0 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -32,6 +32,9 @@ from airflowctl.api.datamodels.generated import (
AssetCollectionResponse,
AssetEventResponse,
AssetResponse,
+ AssetStateStoreBody,
+ AssetStateStoreCollectionResponse,
+ AssetStateStoreResponse,
BackfillCollectionResponse,
BackfillPostBody,
BackfillResponse,
@@ -326,6 +329,39 @@ class AssetsOperations(BaseOperations):
self.client.delete(f"dags/{dag_id}/assets/{asset_id}/queuedEvents")
return asset_id
+ def list_state_store(self, asset_id: str) ->
AssetStateStoreCollectionResponse | ServerResponseError:
+ """List all state store entries for an asset."""
+ return super().execute_list(
+ path=f"assets/{asset_id}/state-store",
data_model=AssetStateStoreCollectionResponse
+ )
+
+ def get_state_store(self, asset_id: str, key: str) ->
AssetStateStoreResponse | ServerResponseError:
+ """Get a single asset state store entry."""
+ self.response = self.client.get(f"assets/{asset_id}/state-store/{key}")
+ return
AssetStateStoreResponse.model_validate_json(self.response.content)
+
+ def set_state_store(self, asset_id: str, key: str, value: str) -> str |
ServerResponseError:
+ """Set an asset state store value. Creates or overwrites the key."""
+ try:
+ parsed_value = json.loads(value)
+ except (ValueError, TypeError):
+ parsed_value = value
+ self.client.put(
+ f"assets/{asset_id}/state-store/{key}",
+
json=AssetStateStoreBody(value=parsed_value).model_dump(mode="json"),
+ )
+ return key
+
+ def delete_state_store(self, asset_id: str, key: str) -> str |
ServerResponseError:
+ """Delete a single asset state store key."""
+ self.client.delete(f"assets/{asset_id}/state-store/{key}")
+ return key
+
+ def clear_state_store(self, asset_id: str) -> str | ServerResponseError:
+ """Delete all state store keys for an asset."""
+ self.client.delete(f"assets/{asset_id}/state-store")
+ return asset_id
+
class BackfillOperations(BaseOperations):
"""Backfill operations."""
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index d6d22bfacc8..ed21902134a 100755
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -510,6 +510,7 @@ class CommandFactory:
"trigger",
"add",
"edit",
+ "set",
"clear",
]
# Datamodels whose generated bool flags follow the datamodel field
defaults instead of
diff --git a/airflow-ctl/src/airflowctl/ctl/help_texts.yaml
b/airflow-ctl/src/airflowctl/ctl/help_texts.yaml
index 4944c60dbdf..b2e5a4129cc 100644
--- a/airflow-ctl/src/airflowctl/ctl/help_texts.yaml
+++ b/airflow-ctl/src/airflowctl/ctl/help_texts.yaml
@@ -28,6 +28,11 @@ assets:
delete-queued-events: "Delete all queued events for a given asset"
delete-dag-queued-events: "Delete all queued asset events for a given Dag"
delete-queued-event: "Delete a specific queued asset event for a given Dag
and asset"
+ list-state-store: "List all state store entries for an asset"
+ get-state-store: "Retrieve a single asset state store entry by its ID and
key"
+ set-state-store: "Set an asset state store value. Parsed as JSON if valid,
otherwise stored as a string"
+ delete-state-store: "Delete a single asset state store key by its ID and key"
+ clear-state-store: "Delete all state store keys for an asset by its ID"
backfill:
create: "Create a backfill job for a given Dag ID and date range"
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index 7806fcfb5ab..6e52ba1a049 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -36,6 +36,8 @@ from airflowctl.api.datamodels.generated import (
AssetCollectionResponse,
AssetEventResponse,
AssetResponse,
+ AssetStateStoreCollectionResponse,
+ AssetStateStoreResponse,
BackfillCollectionResponse,
BackfillPostBody,
BackfillResponse,
@@ -321,7 +323,11 @@ class TestAssetsOperations:
queued_events=[asset_queued_event_response],
total_entries=1,
)
-
+ asset_state_store_response = AssetStateStoreResponse(
+ key="my_key",
+ value={"my_val": 0}, # type: ignore[arg-type]
+ updated_at=datetime.datetime(2025, 1, 1, 0, 0, 0),
+ )
dag_run_response = DAGRunResponse(
dag_display_name=dag_id,
dag_run_id=dag_id,
@@ -530,6 +536,73 @@ class TestAssetsOperations:
response = client.assets.delete_queued_event(dag_id=self.dag_id,
asset_id=self.asset_id)
assert response == self.asset_id
+ def test_list_state_store(self):
+ collection_response = AssetStateStoreCollectionResponse(
+ asset_state_store=[self.asset_state_store_response],
+ total_entries=1,
+ )
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.url.path ==
f"/api/v2/assets/{self.asset_id}/state-store"
+ return httpx.Response(200,
json=json.loads(collection_response.model_dump_json()))
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.assets.list_state_store(self.asset_id)
+ assert response == collection_response
+
+ def test_get_state_store(self):
+ key = self.asset_state_store_response.key
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.url.path ==
f"/api/v2/assets/{self.asset_id}/state-store/{key}"
+ return httpx.Response(200,
json=json.loads(self.asset_state_store_response.model_dump_json()))
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.assets.get_state_store(self.asset_id, key)
+ assert response == self.asset_state_store_response
+
+ @pytest.mark.parametrize(
+ ("value", "expected"),
+ [
+ ('{"index": 0}', {"index": 0}),
+ ("hello", "hello"),
+ ],
+ )
+ def test_set_state_store(self, value, expected):
+ key = self.asset_state_store_response.key
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.method == "PUT"
+ assert request.url.path ==
f"/api/v2/assets/{self.asset_id}/state-store/{key}"
+ assert json.loads(request.content) == {"value": expected}
+ return httpx.Response(204)
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.assets.set_state_store(self.asset_id, key, value)
+ assert response == key
+
+ def test_delete_state_store(self):
+ key = self.asset_state_store_response.key
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.method == "DELETE"
+ assert request.url.path ==
f"/api/v2/assets/{self.asset_id}/state-store/{key}"
+ return httpx.Response(204)
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.assets.delete_state_store(self.asset_id, key)
+ assert response == key
+
+ def test_clear_state_store(self):
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ assert request.method == "DELETE"
+ assert request.url.path ==
f"/api/v2/assets/{self.asset_id}/state-store"
+ return httpx.Response(204)
+
+ client = make_api_client(transport=httpx.MockTransport(handle_request))
+ response = client.assets.clear_state_store(self.asset_id)
+ assert response == self.asset_id
+
class TestBackfillOperations:
backfill_id: NonNegativeInt = 1