Skip to content

consistent typing for lists - #457

Merged
wpbonelli merged 10 commits into
modflowpy:developfrom
wpbonelli:union-fields
Oct 13, 2026
Merged

wpbonelli merged 10 commits into
modflowpy:developfrom
wpbonelli:union-fields

Conversation

@wpbonelli

@wpbonelli wpbonelli commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Use MODFLOW-ORG/modflow-devtools#389 and MODFLOW-ORG/modflow-devtools#391 for consistent list field typing

Up to now some packages' period data has been typed as a list of records, where the record is just a leading feature number followed by a union. Codegen used to copy the index into each of the union arms to artificially produce a list of unions. This gave a slightly less verbose API at the cost of harder-to-predict typing. Remove this so the generated field typing follows the DFNs predictably.

# before
Maw.Status(ifno=0, status="active")
Maw.HeadLimit(ifno=1, head_limit="off")

# after
Maw.StressPeriodData(ifno=0, mawsetting=Maw.Status(status="active"))
Maw.StressPeriodData(ifno=1, mawsetting=Maw.HeadLimit(head_limit="OFF"))

# mixing typed and raw format works too
Maw.StressPeriodData(ifno=0, mawsetting=("status", "active")).

Plain tuples like (0, "STATUS", "ACTIVE") can still be passed instead of the typed form. This all applies to MAW, LKT, LKE, SFR, SPC, TVK and TVS

Other changes:

  • OC settings become literals, the quickstart changed because of this
  • str fields no longer accept numbers, that must be typed as a union

wpbonelli and others added 6 commits October 12, 2026 10:48
devtools now derives `tagged` on unions and records: a union is tagged if
every arm begins with a keyword. LAK's `bedleak` and MAW's `head_limit`
become untagged unions of a word (NONE, OFF) and a number. Track both:

- Codegen tells a union column (each arm one value: OBS's id, `bedleak`)
  from a keystring (arms are line forms) by its arms, not by `tagged`,
  which LAK's `laksetting` (arms led by a lake number) no longer sets.
- A keyword arm types as a literal: `Union[Literal["NONE"], float]`. It
  reads inline, matched in any case, else as a number.
- An arm of a keyword and one value names the value after the keyword,
  like a tagged scalar arm, so MAW's `HeadLimit.head_limit` keeps its name.
- `_is_instance` checks every arm of a union, so `str` fields no longer
  need to accept numbers.
- The grammar prefixes a tagged `valid` string's name itself, since the
  union made of it no longer says it's tagged. Grammars are unchanged.
- Bump modflow-devtools to 7df4ae97.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tagged list's record (SCALE <factor>) keeps its value's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A union arm types by its kind: a keyword is a literal word, an untagged
  value its own type, and a record or tagged value a class. OC's
  ocsetting is `Literal['ALL', 'FIRST', 'LAST'] | Oc.Frequency |
  Oc.Steps`; `Oc.All`, `Oc.First` and `Oc.Last` are gone. This replaces
  `is_column_union`.
- A list's element class mirrors the DFN's element. A record with a union
  field (LKT, LKE, MAW, SFR, SPC, TVK, TVS) is one row class holding the
  index and the union, not one class per arm with the index copied in:
  `Lkt.StressPeriodData(ifno=0, laksetting=Lkt.Status(status="active"))`.
  Flat tuples like `(0, "STATUS", "ACTIVE")` still work. LAK is wrapped
  too, until devtools makes its element the union itself, as PRP's is.
- A row's children include those in its union arm (SFR's CROSS_SECTION
  TAB6 file).
- Static list columns no longer take a block prefix for sharing a name
  with a period setting keyword; period settings aren't package
  attributes, so nothing collided.
- Drop the "keystring" term (v1's union type) from code, docs and tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devtools now makes LAK's period element its laksetting union (modflowpy#391), so
LAK's rows are its arms again, as before. Also put `# noqa: E501` only
on long union annotations, not on every column ruff wraps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Maw.StressPeriodData(ifno=0, mawsetting=("status", "active"))` was
accepted and written as a raw tuple. A union field with arm classes now
converts, on init and on assignment, a tuple to the arm its keyword
picks, or a word to the word, and raises if no arm takes the value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpbonelli wpbonelli added this to the MVP milestone Oct 12, 2026
wpbonelli and others added 3 commits October 12, 2026 16:37
devtools' `mf sync` now fails if any registry does, and the mf6/example
models registry is missing its models.toml. Fail on a DFN sync failure,
but only warn on a models one for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pwsh exits with the last native command's code, so Windows still failed
on the models sync. Pass GITHUB_TOKEN for the DFN sync's API call, which
devtools will send once it authenticates that lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpbonelli wpbonelli changed the title consistent typing for lists of unions consistent typing for lists Oct 13, 2026
@wpbonelli
wpbonelli marked this pull request as ready for review October 13, 2026 00:25
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpbonelli
wpbonelli merged commit 30c7e43 into modflowpy:develop Oct 13, 2026
17 checks passed
@wpbonelli
wpbonelli deleted the union-fields branch October 13, 2026 01:17
wpbonelli added a commit that referenced this pull request Oct 13, 2026
Follow-up to #457. The OC reshape missed PRP which has the same form

# before
Prp(stress_period_data={0: [Prp.All(), Prp.Frequency(2)]})
# after
Prp(stress_period_data={0: ["ALL", Prp.Frequency(2)]})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant