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]

Reply via email to