Shawnsuun opened a new pull request, #74383:
URL: https://github.com/apache/airflow/pull/74383
`airflowctl dags update <dag_id>` without `--is-paused` / `--no-is-paused`
unpauses a paused Dag with no warning. Generated boolean flags default to
`False`, so the CLI sends `{"is_paused": false, "scheduling_state": null}`, and
the server accepts it as an unpause.
The same default makes `dags update --scheduling-state <state>` unusable. It
always sends `is_paused: false` alongside the state, so the server's "exactly
one of `is_paused` or `scheduling_state`" validator rejects it with a 422.
`CommandFactory` already has a list of datamodels whose generated bool flags
keep the datamodel field default instead of `False`
(`field_bool_default_datamodels`; `ClearTaskInstancesBody` is there so a bare
`tasks clear` keeps `dry_run=True`). This PR adds `DAGPatchBody` to that list,
so an omitted `--is-paused` stays `None`:
- A bare `dags update` is now rejected by the server (422, "Exactly one of
`is_paused` or `scheduling_state` must be provided") and the Dag keeps its
state.
- `dags update --scheduling-state <state>` works.
- `--is-paused` / `--no-is-paused`, and `dags pause` / `unpause` / `drain`,
behave as before.
Alternative considered: a CLI-side check that refuses `dags update` without
a flag. I didn't do that because it would repeat a rule the server already
enforces and add special-casing to the generated-command path.
### Reproduction
I ran these against a local api-server built from `main` (SQLite, simple
auth manager), with one Dag that starts paused. The `>>>` lines show the PATCH
body airflowctl sent. Each case starts from a paused Dag.
Before:
```
airflowctl at main (3e302fd257)
$ airflowctl dags pause repro_dag
-> Dag state: is_paused=True scheduling_state=paused
$ airflowctl dags update repro_dag
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":false,"scheduling_state":null}
<<< 200
-> Dag state: is_paused=False scheduling_state=active
$ airflowctl dags update repro_dag --scheduling-state paused
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":false,"scheduling_state":"paused"}
<<< 422 Value error, Exactly one of `is_paused` or `scheduling_state` must
be provided
-> Dag state: is_paused=True scheduling_state=paused
$ airflowctl dags update repro_dag --no-is-paused
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":false,"scheduling_state":null}
<<< 200
-> Dag state: is_paused=False scheduling_state=active
```
After:
```
airflowctl at main (3e302fd257) + this PR
$ airflowctl dags pause repro_dag
-> Dag state: is_paused=True scheduling_state=paused
$ airflowctl dags update repro_dag
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":null,"scheduling_state":null}
<<< 422 Value error, Exactly one of `is_paused` or `scheduling_state` must
be provided
-> Dag state: is_paused=True scheduling_state=paused
$ airflowctl dags update repro_dag --scheduling-state paused
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":null,"scheduling_state":"paused"}
<<< 200
-> Dag state: is_paused=True scheduling_state=paused
$ airflowctl dags update repro_dag --no-is-paused
>>> PATCH /api/v2/dags/repro_dag?
body={"is_paused":false,"scheduling_state":null}
<<< 200
-> Dag state: is_paused=False scheduling_state=active
```
I also checked an Airflow 3.3.2 api-server. There, `is_paused` is a required
`bool`, so the `null` sent by a bare `dags update` is rejected with a 422 too.
Separately, current `main` airflowctl already gets a 422 from 3.3.2 for every
`dags update`/`pause`, because it sends `"scheduling_state": null`, which 3.3.2
rejects as an extra field. That issue exists without this PR and is left for a
follow-up.
### Tests
- `uv run --project airflow-ctl pytest airflow-ctl/tests -q`: 426 passed.
Both new test cases fail without the change.
- `prek run mypy-airflow-ctl --all-files`: passed
- `prek run --stage pre-commit --files <changed files>`: passed, except
`generate-airflowctl-help-images`, which needs a working breeze locally. Help
output is unchanged by this PR: the `dags` group help hash is identical with
and without the change.
No newsfragment, because airflow-ctl doesn't use them.
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes (please specify the tool below)
Generated-by: GitHub Copilot CLI (Claude Opus 5.5) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
---
* Read the **[Pull Request
Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)**
for more information. Note: commit author/co-author name and email in commits
become permanently public when merged.
* For fundamental code changes, an Airflow Improvement Proposal
([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals))
is needed.
* When adding dependency, check compliance with the [ASF 3rd Party License
Policy](https://www.apache.org/legal/resolved.html#category-x).
* For significant user-facing changes create newsfragment:
`{pr_number}.significant.rst`, in
[airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
You can add this file in a follow-up commit after the PR is created so you
know the PR number.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]