Skip to content

Accept legacy request field names alongside new canonical ones (fixes asset-level force_new_job_creation gap)#2352

Open
Flix6x wants to merge 2 commits into
mainfrom
feat/backward-compat-request-field-aliases
Open

Accept legacy request field names alongside new canonical ones (fixes asset-level force_new_job_creation gap)#2352
Flix6x wants to merge 2 commits into
mainfrom
feat/backward-compat-request-field-aliases

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 22, 2026

Copy link
Copy Markdown
Member

Description

Follow-up to the API v4 discussion (#2349): for request fields (unlike response fields), a legacy field name can keep working immediately, without waiting for a new API version — a client can accept both spellings via a @pre_load hook. This PR generalizes a pattern that already existed for exactly one schema and applies it to close a real, confirmed gap.

The concrete bug this fixes

POST /assets/<id>/schedules/trigger (AssetTriggerSchema) never accepted the legacy force_new_job_creation field name — only force-new-job-creation. The sensor-level trigger endpoint already had this backward-compat shim (added in #2182), but it was never applied to the asset-level one.

This is exactly why flexmeasures-client#218 had to add client-side server-version sniffing to decide which field name to send — its own PR description documents the asset endpoint returning a 422 when sent the legacy key. With this fix, the server accepts both, and that client-side workaround becomes unnecessary (flexmeasures-client does not need to be updated for this PR to be compatible).

What changed

  • Added SupportsLegacyFieldAliases, a small @pre_load mixin (flexmeasures/data/schemas/utils.py), generalizing the one-off alias hook that already existed in GetSensorDataFilterSchemaMixin (sensor_data.py) for source-account/source-type. A schema declares legacy_field_aliases = {"old_key": "new-data-key"} and both spellings are accepted; only the canonical one is deserialized.
  • Applied it to:
    • AssetTriggerSchema.force_new_job_creationforce-new-job-creation (the actual bug fix above).
    • TriggerScheduleKwargsSchema.force_new_job_creation (sensor-level) — replaced its hand-rolled equivalent with the shared mixin, no behavior change.
    • PaginationSchema: per_page/sort_by/sort_dir now also accept per-page/sort-by/sort-dir (used by every paginated list endpoint).
    • AssetAPIQuerySchema, UserAPIQuerySchema, SensorKwargsSchema: account_id/asset_id now also accept account/asset (candidate canonical names from the naming policy in #2127).
    • A few PaginationSchema subclasses (AssetPaginationSchema, AssetAuditLogPaginationSchema, UserAuditlogSchema, AccountAPIQuerySchema) were overriding sort_by/sort_dir without the parent's data_key, silently losing the hyphenated spelling — fixed as part of this pass.
  • Updated documentation/api/change_log.rst.

What this doesn't do

Response fields aren't touched here — a response can't "accept either" key, so response-side backward compatibility means including both keys in the output (as #2224/#2350 did for job/schedule). That's a separate, larger effort tracked in #2349 for generic_asset_id, flex_model/flex_context, consultancy_account_id, parent_asset_id, and the account color/logo fields.

How to test

  • Existing sensor-level force_new_job_creation backward-compat tests (test_sensor_schedules_fresh_db.py) continue to cover that field via the now-shared mixin.
  • Verified each schema's alias mapping directly against _apply_legacy_field_aliases for all affected schemas (PaginationSchema and its subclasses, AssetAPIQuerySchema, UserAPIQuerySchema, SensorKwargsSchema, AssetTriggerSchema) — legacy keys map to the correct canonical data_key.
  • No new integration test was added for the asset-level force_new_job_creation fix specifically; flagging that as a gap — happy to add one if desired.
  • Triggering the QA release workflow (docker-qa.yml) against this branch with flexmeasures-client-ref=main (unmodified), to confirm the HEMS walkthrough and tutorials still pass without any client-side changes.

Adds a shared `SupportsLegacyFieldAliases` pre_load mixin
(flexmeasures/data/schemas/utils.py) and applies it so several request
fields now accept both spellings without breaking existing clients:

- `force_new_job_creation` / `force-new-job-creation` on the asset-level
  scheduling trigger (previously only the sensor-level endpoint accepted
  both; this is what flexmeasures-client#218 had to work around
  client-side with server-version sniffing).
- `per_page`/`per-page`, `sort_by`/`sort-by`, `sort_dir`/`sort-dir` on all
  paginated list endpoints.
- `account_id`/`account` and `asset_id`/`asset` on GET /sensors,
  GET /assets, GET /users query filters.

Response fields still need explicit dual-field output for backward
compatibility (can't "accept either" on the way out), but request fields
don't -- this generalizes the one-off alias hook that already existed for
source-account/source-type filtering, so future field renames on the
request side can ship immediately, without waiting for a new API version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@read-the-docs-community

read-the-docs-community Bot commented Jul 22, 2026

Copy link
Copy Markdown

_apply_legacy_field_aliases unconditionally rebuilt the incoming request
data into a plain dict, even when none of its declared legacy aliases
were present. That silently broke AssetTriggerSchema.normalize_flex_context_format,
which detects a multi-commodity flex-context list via MultiDict.getlist() --
CI caught this via test_asset_trigger_with_multi_commodity_flex_context
failing (the scheduling job never finished, since flex-context normalization
saw a single dict instead of the intended commodities list).

Now the hook only rebuilds `data` when one of its own legacy keys is
actually present, leaving MultiDict-like inputs untouched otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant