DHBhensdadia opened a new pull request, #71333:
URL: https://github.com/apache/airflow/pull/71333
`airflowctl connections update --connection-id pg_prod --conn-type postgres
--host db2.example.com`
erases that connection's login, port, schema and description.
The API applies every key present in a PATCH body, so a field the user never
typed arrives as `null`
and is stored as one. argparse fills an omitted flag with `None`, and the
generated command builds
the whole datamodel from those values, so by the time the body is serialized
the client can no longer
distinguish "not supplied" from "set to null".
The `create` methods in `operations.py` already pass `exclude_none=True`;
the `update` methods did
not. This is the same omission reported in #70327 for `dags trigger`, where
it shows up as a request
older API servers reject rather than as lost data.
### Reproduction
Against a real Airflow 3.4.0 API server on SQLite (no Docker):
```
$ airflowctl connections update --connection-id pg_prod --conn-type postgres
--host db2.example.com
before
{"connection_id":"pg_prod","conn_type":"postgres","description":"primary
warehouse",
"host":"db1.example.com","login":"airflow","schema":"analytics","port":5432}
after {"connection_id":"pg_prod","conn_type":"postgres","description":null,
"host":"db2.example.com","login":null,"schema":null,"port":null}
```
```
$ airflowctl variables update --key api_endpoint --value
https://staging.example.com
before
{"key":"api_endpoint","value":"https://prod.example.com","description":"prod
endpoint - owned by data team"}
after
{"key":"api_endpoint","value":"https://staging.example.com","description":null}
```
```
$ airflowctl pools update --pool etl_pool --slots 42
before {"name":"etl_pool","slots":5,"description":"ETL workers - do not
delete"}
after {"name":"etl_pool","slots":42,"description":null}
```
With this change, each of those commands sends only the fields the user
passed, and the untouched
fields keep their stored values.
### Scope
`PoolsOperations.update` still sends `include_deferred: false`, because
`False` is not `None`. That
has a different cause — `_get_bool_arg_default` in `ctl/cli_config.py`
returns `False` for every
datamodel outside its one-entry allowlist instead of the field's declared
default — and is left for a
separate PR.
Also deliberately unchanged:
- `DagsOperations.update` — `DAGPatchBody` has a single required `bool`
field, so `exclude_none` would
be a no-op with nothing to test.
- The `bulk` methods — they PATCH a collection endpoint with a body of
actions, where a `null` inside
an action may be meaningful. Different semantics.
- `ConnectionsOperations.test` — POST, not PATCH; it does not mutate
stored state.
- `default_pool` remains un-updatable through `airflowctl` (`Only slots and
included_deferred can be
modified on Default Pool`). The server only permits it when `update_mask`
is sent, and the client
never sends one. Separate fix.
`test_update_uses_schema_alias_in_request_body` asserted the old body
including its `null` keys; its
expectation is updated. Its subject — that the `schema` alias is used
rather than `schema_` — is
unchanged and still asserted.
related: #70327
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes (please specify the tool below)
Claude Code (Opus 5) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
--
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]