Repository navigation
Fix the remaining type-checking bugs from #66 (#72 T1) - #76
Draft
ryan-kipawa wants to merge 35 commits into
Draft
ryan-kipawa wants to merge 35 commits into
ryan-kipawa wants to merge 35 commits into
Conversation
The tests were strict xfails; they fail until the wrapper reads the return code the same way as the other eum wrappers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native function returns non-zero while there is a next unit and 0 after the last one. The wrapper treated non-zero as failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetElementIds was a strict xfail: it never stores the ids. Int64 node ids, as np.array of Python ints gives on Linux, fail in CreateFile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetElementIds checked the length of the ids but never kept them. Both setters now convert to int32, the type the file stores, so int64 ids no longer fail in CreateFile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_MeshBuilder_keeps_z_precision was a strict xfail, and the Oresund rebuild test compared z only to float32 precision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mesh files store z as doubles, and SetNodes already kept x and y as float64. DfsuBuilder's float32 z and MeshBuilder.Create(dfsuFile) are unchanged, since dfsu stores z as float32. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The minutes test was a strict xfail: the second row of a 10 minute file is stamped 00:00:10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadItemTimeStepNext returns times in the time axis unit; Dfs0ToAscii added them to the start as seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_read_static_item_zero was a strict xfail; it now also covers -1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfsFindItemStatic returns 0 for 0 and negative numbers and positions on the first static item. Its return code is left unchecked: it is 0 one past the last item and 1002 further out, and both already read as None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadItemTimeStepNext and ReadItemTimeStep take reshape but DfsFile ignores it; only Dfs123File reshapes. 0-D and 1-D items stay flat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reshape parameter was ignored unless the file was a Dfs123File. Reshape with the item's own spatial axis, in Fortran order as Dfs123File does. Dfs123File passes no reshape to the base method, so its behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_dynamic_item_builder_can_be_reused was a strict xfail. New tests check that changing a dynamic or static item builder after it returned an item does not change that item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetDynamicItemInfo swapped in a blank DfsDynamicItemInfo while keeping the 'is set' flags, so a reused builder returned an empty item. Both builders now return the configured item and keep a shallow copy, with their own associated static item list or data array, as the existing comment intends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_spectral_0d_file_has_one_spectrum was a strict xfail. New tests cover a file with only frequencies, and Validate rejecting a Spectral0D builder with no frequencies or directions, or with an element count that does not match them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetupBuilder had no branch for Spectral0D, so CreateFile raised an Exception with no message. Write data type 2001 with the MIKE_FM block [nodes, elements, dims, 0, nf, nd] that DfsuFile reads as Spectral0D: dims 2 for a frequency-direction mesh, 1 for frequencies or directions only. Validate checks the mesh has one element per spectral value, so the file is not read back as Dfsu2D or Dfsu1D. The fallback exception now names the file type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetNodeIds accepts None to use the default ids; SetElementIds raised. Ids that do not fit in int32 were silently wrapped by the conversion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetElementIds(None) raised, while SetNodeIds(None) goes back to the default ids. Converting with np.asarray(..., dtype=np.int32) wrapped ids that do not fit, so both setters now raise ValueError instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int32
DfsuBuilder's id setters restore the default ids on None and reject ids
that don't fit in int32. MeshBuilder.SetElementIds, the same setter for
mesh files, raises TypeError on None although its docstring calls the ids
optional. It writes ids outside int32 into a file that MeshFile.ReadMesh
then can't load ("Python integer ... out of bounds for int32").
The ids-are-written test now runs over lists, int32 and int64 arrays and
the int32 bounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MeshBuilder.SetElementIds gets the same handling as DfsuBuilder's id setters: None goes back to the default ids 1, 2, ..., and ids are stored as int32, raising ValueError for ids that don't fit instead of writing a mesh file that MeshFile.ReadMesh can't read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetFromMeshFile passes the mesh's ids to the same writer as SetNodeIds and SetElementIds. MeshBuilder makes its default ids with np.arange, which is int64, so a dfsu built from MeshBuilder.CreateMesh() fails in CreateFile with "Data type not supported: int64". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ids go through the same int32 conversion and range check as SetNodeIds and SetElementIds, so a mesh with int64 ids, such as one from MeshBuilder.CreateMesh(), can be written. A mesh with no ids still gets the default ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Binding Dfs0ToAscii's time axis once removes 3 warnings, so the count falls from master's 328 to 325. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ryan-kipawa
force-pushed
the
task/72-T1
branch
from
October 13, 2026 14:42
933c3b2 to
1c48e76
Compare
The two builders had the same __Int32Ids, differing only in the name at the start of the message. Move it to a module-level _Int32Ids in MeshBuilder.py, which DfsuBuilder already imports MeshNodes from, and pass the name from each call. The messages are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DfsuBuilder.SetNodeIds and SetElementIds, and MeshBuilder.SetElementIds, accept a 2x2 array for four nodes, an (n, 1) array for n elements, and booleans, which they convert to 0 and 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared _Int32Ids checked only that the ids fit in int32, so a 2-D array of the right size, or booleans as 0 and 1, were stored. Check the dimensions and the dtype first, so DfsuBuilder and MeshBuilder reject them the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ads them CreateMesh makes the default node and element ids with np.arange, which is int64 on Linux, so a built mesh's id dtype depends on whether ids were set. Once they are int32, MeshBuilder no longer gives SetFromMeshFile int64 ids, so test that path with int64 ids from MeshFile.Create, which keeps the ids it is given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
np.arange(n) + 1 is int64 on Linux. Make the defaults int32, as MeshFile.ReadMesh reads ids, so a built mesh has the same id dtype whether or not ids were set. SetFromMeshFile keeps its int32 cast, as MeshFile.Create can still give it int64 ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetFrequencies and SetDirections store a list as given. Validate accepts it, then CreateFile fails with AttributeError: 'list' object has no attribute 'size', leaving a half-written file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetFrequencies and SetDirections stored what they were given, and CreateDfsu writes them with AddCreateStaticItem, which needs an array. Convert them in the setters, as doubles, the type DfsuFile reads the Frequency and Direction static items as. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_generic_read_stays_flat_without_an_axis stood in for a file whose axis GetItemSpatialAxis reads as None, such as EqD4. No such file exists: DfsFactory cannot make EqD4, NeqD or TvarD axes, and a file given an EqD4 axis through the native dfsSetItemAxisEqD4 fails to open with 'ValueError: 11 is not a valid SpaceAxisType', because SpaceAxisType has no member for it. GetItemSpatialAxis returns a value or raises for every member, so the None branch cannot be reached from a file, and the test only exercised the hand-set value. The guard stays because the return type allows None; the comment now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadStaticItem(2**31) wraps in the untyped dfsFindItemStatic call and returns 'Node id', and 2**64 raises ctypes.ArgumentError. Numbers that do not fit in int32 are past the last item, which returns None. 2**31 - 1 is the guard that already returns None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfsFindItemStatic is called without argtypes, so ctypes wrapped 2**31 to a valid item number and raised ArgumentError beyond 64 bits. No file has that many static items, so return None, as for any other number past the last item, before calling the native library. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetStaticItem copies the data array into the builder's next item and returns the caller's own array, so changing the array passed to SetData changes the item already returned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetStaticItem gave the builder's next item a copy of the data and returned the item holding the caller's array, so changing that array still changed the returned item. Return a copy of the item with its own copy of the data instead, and keep the builder's item as it is. This is still one copy per call, which the invariant needs: the builder keeps its data for the next item, and that array, as ItemInfo.Data, can be changed in place. AddStaticItem writes the data to the file at once, so it needs no copy of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #72
What and why
Adding type checking in #66 turned up bugs that were parked as 8 strict
xfailtests:eumGetNextEqvUnitreads its return code the wrong way round, andDfsuBuilder.SetElementIdsnever stores the ids.MeshBuilderrounds z to float32.Dfs0ToAsciitreats minutes as seconds, andReadStaticItem(0)returns the first item. A reusedDfsDynamicItemBuilderreturns an empty item, andDfsuBuildercan't write Spectral0D files. This PR fixes each one with a test commit and then a fix commit, and removes everyxfailfromtests/. It also fixes the two related bugs the plan added:reshape=Truewas ignored on a genericDfsFile, andDfsStaticItemBuilderchanged items it had already returned. Two more from #72,SetDeleteValue*and theValidateencode-key index, were fixed in #74.Scope, by mechanism rather than by the reported case:
eumGetNextEqvUnitwas the only wrapper that read the native result inverted. The other 28 calls ineum.pythat check a result all treat non-zero as success, and 2 return counts.DfsuBuilder.SetNodeIds,SetElementIdsandSetFromMeshFile, andMeshBuilder.SetElementIds, which is the side finding below.SetFromMeshFilematters becauseMeshFile.Createkeeps the ids it is given, so a dfsu built from a mesh with int64 ids failed inCreateFileas well.MeshBuilder.CreateMesh()made its default ids int64 on Linux; they are now int32 too. The setters also reject ids that are not a 1-D array, or are booleans. See "Why int32" below.Dfs0ToAscii: tested on every time unitDfsTemporalAxisnames (milliseconds, seconds, minutes, hours, days), on both equidistant and non-equidistant calendar axes.ReadItemTimeStepNextandReadItemTimeStep, on 0-D, 1-D, 2-D and 3-D items. An item with no axis can't come from a file (see Deviations), so that guard is untested.ReadStaticItem: covers every number below 1, every item, past the last item, and past int32.Side findings, each with its own tests:
DfsuBuilder.SetNodeIdswith int64 ids, which is whatnp.array([...])gives on Linux, failed inCreateFilewith "Data type not supported: int64". Both id setters now store int32, and they raiseValueErrorfor ids that don't fit instead of wrapping them.DfsuBuilder.SetElementIds(None)raised; it now goes back to the default ids, asSetNodeIds(None)does.MeshBuilder.SetElementIds(None)raisedTypeError, although its docstring calls the ids optional. Ids outside int32 were written to a mesh file thatMeshFile.ReadMeshthen couldn't load. It now behaves like theDfsuBuildersetters.From the review of 1c48e76, each with its own tests:
MeshBuilder.CreateMesh()made its default ids int64 on Linux, so a built mesh's id dtype depended on whether ids were set. They are int32 now, asMeshFile.ReadMeshreads them.DfsuBuilder.SetFrequenciesandSetDirectionsstored lists as given.Validatepassed them, thenCreateFilefailed withAttributeErrorand left a half-written file. They are stored as float64 arrays now.ValueErrornow, through one helper both builders share.ReadStaticItem(2**31)wrapped in the untyped ctypes call and returned 'Node id', and2**64raisedctypes.ArgumentError. Numbers past int32 returnNone, as numbers past the last item do.GetStaticItemreturned the item holding the caller's array, so changing the array passed toSetDatachanged it. The returned item now has its own copy.Commits
a3f247bExpect eumGetNextEqvUnit to report units while rc is non-zero: removes the 2 eum xfails and adds an oracle test againsteumUnitsEqv.4d5153cRead eumGetNextEqvUnit's return code like the other eum wrappers:rc != 0→rc == 0.0d49b2dExpect DfsuBuilder to write the element and node ids it is given: removes the xfail; int32/int64 for both setters.b169d7aStore DfsuBuilder element ids, and node and element ids as int32: the missingself.__elementIds = ....5a63c43Expect MeshBuilder to keep z as given: removes the xfail and makes the Oresund rebuild compare z exactly.fdbc893Keep z as float64 in MeshBuilder.SetNodes: a one-word change.DfsuBuilderandCreate(dfsuFile)are left alone.0cbcc7dExpect Dfs0ToAscii to stamp rows in the time axis unit: removes the xfail; 5 units × 2 calendar axis types.1b82f89Convert Dfs0ToAscii item times with the time axis's ToSeconds: also binds the time axis once, which removes 3 pyrefly warnings.3a52a74Expect ReadStaticItem to reject item numbers below 1: removes the xfail; tests 0 and -1, and every number from 1 to 9.6340f38Reject static item numbers below 1 in ReadStaticItem: the commit message says why the return code stays unchecked.602237bTest reshape on items read through a generic DfsFile: dfs2/dfs3 must matchDfs2File/Dfs3File. Guards check that 0-D/1-D items, items without an axis, and anything read withoutreshapestay flat.7b59a9eReshape 2-D and 3-D items in DfsFile.ReadItemTimeStepNext: thelen(shape) >= 2rule, applied only whenreshapeis set and the item has an axis.bee7614Expect reused item builders to keep settings and leave items alone: removes the xfail; one invariant test over every setter of both builders.37bf124Keep a private copy of the item in DfsBuilder's item builders:copy.copyplus its own list or data array.825d654Expect DfsuBuilder to write and validate Spectral0D files: removes the xfail; adds frequencies-only and directions-only files andValidateerrors.ccf25e3Write DfsuSpectral0D files in DfsuBuilder: adds theValidateandSetupBuilderbranches, and gives the fallbackraisea message.dd5e32dExpect SetElementIds(None) to restore defaults, and int32 range checks: the int32 bounds, one past each end, and ids beyond 64 bits.6f82e0aRestore default element ids on None, and reject ids outside int32:DfsuBuilder.__Int32Ids.72db4ebExpect MeshBuilder.SetElementIds to take None and reject ids outside int32: side finding.f201509Restore default mesh element ids on None, and reject ids outside int32:MeshBuilder.__Int32Ids.d5afdacExpect DfsuBuilder to write the ids of a mesh built by MeshBuilder: the third entry point that reaches the id writer.04707faStore the ids SetFromMeshFile takes from a mesh as int32:Nonestill gives the defaults.1c48e76Lower the pyrefly warning ceiling to 325: master's 328 → 325.Review fixes on 1c48e76:
cc9bc10Share one int32 id helper between MeshBuilder and DfsuBuilder:_Int32Ids(ids, name)inMeshBuilder.py; messages unchanged.50c9a3bExpect id setters to reject 2-D, scalar and boolean ids: both builders.43f3287Reject 2-D, scalar and boolean ids in the id setters: two checks in the shared helper.4a4a81eExpect MeshBuilder's default ids to be int32, as MeshFile.ReadMesh reads them: also testsSetFromMeshFilewith int64 ids fromMeshFile.Create, the path the int32 cast still covers.c8133e0Make MeshBuilder's default ids int32:np.arange(1, n + 1, dtype=np.int32).d628cc8Expect Spectral0D files to be written from frequency and direction lists: read back withDfsuFile.ae26532Store DfsuBuilder frequencies and directions as float64 arrays:np.asarray(..., dtype=np.float64)in the setters.be550ecDrop the reshape test that set an item's axis to None by hand: no file can have such an axis (see Deviations).7485ae7Expect ReadStaticItem to return None for numbers past int32: 2³¹−1 (guard), 2³¹, 2³²+1, 2⁶⁴.10f5585Return None from ReadStaticItem for numbers past int32: returns before the native call.d366890Expect a static item to keep its data when the caller changes the array.3273233Copy the data into the static item GetStaticItem returns: the copy moves from the builder's item to the returned one.Code Review Guide
Glossary
@pytest.mark.xfail(strict=True). The test must fail, and it turns red if it passes. Each one here recorded a suspected bug from Add pyrefly type checking #66.[nodes, elements, dimensions, layers, (sigma layers | frequencies, directions)].DfsuFilereads the file type from it and the data type (2001 dfsu, 2002 1-D spectral, 2003 2-D spectral).DfsuFilerecognises it as data type 2001 with nf·nd (dims 2), or nf or nd (dims 1), equal to the element count.order="F": Fortran (column-major) reshape, whichDfs123Filealready uses so that x varies fastest.Where the change sits
Green nodes are changed.
DfsuFile,MeshFileandDfs123Fileare the unchanged code the changes pair with: the readers that must read the files back, and the override that must keep its own reshape.Conventions followed
ValueErrorDfsFile.py:1479
DfsFile.py:953
MeshBuilder.py, whichDfsuBuilderimportsMeshBuilder.py:12
MeshBuilder.py:21
[nodes, elements, dims, 0, nf, nd]DfsuBuilder.py:633
DfsuBuilder.py:648
Dfs123File.py:24
DfsFile.py:1075
Deviations
DfsBuilder.py:676 (unchanged)
DfsBuilder.py:697
DfsuBuilder.py:437
DfsuBuilder.py:418
MeshBuilderno longer has its own copy of__Int32Ids. Rounds 1 to 5 declined a shared helper, because each builder kept its private helpers as name-mangled static methods. The review of 1c48e76 asked for one, and the 1-D and boolean checks would otherwise have been written twice, so both builders now call_Int32Ids(ids, name)inMeshBuilder.py(cc9bc10). The messages are unchanged.DfsuBuilder.py:6
MeshBuilder.py:120
DfsFactorycan't make EqD4, NeqD or TvarD axes, and a dfs2 given an EqD4 axis through the nativedfsSetItemAxisEqD4fails to open withValueError: 11 is not a valid SpaceAxisType.GetItemSpatialAxisreturns an axis or raises for everySpaceAxisTypemember. The earlier test setSpatialAxis = Noneby hand, which CODING_STANDARDS.md doesn't accept, so it was removed (be550ec). Theaxis is not Noneguard stays because the return type allowsNone. Alternative: none that uses a real file.DfsFile.py:1788 (unchanged)
DfsFile.py:1075
DfsuFiletells Spectral0D apart by the MIKE_FM counts only, and no public source says which element shapes a frequency-only or direction-only point spectrum uses (a 1-D axis might well use 2-node elements). Alternative: require triangles or quadrilaterals, which needs a file written by a MIKE engine to confirm.Dfs0ToAsciiraises when the file has no time axis (not asked for). Why: it reads the axis once, which removes 3 pyrefly warnings; it used to fail withAttributeErroronNone. Alternative:assert. It uses a plainException, like the function's other raise.Dfs0ToAscii.py:51
Dfs0ToAscii.py:9
Why int32
The id setters and
MeshBuilder's defaults use int32 because that is the range the readers enforce. The dfsu and mesh formats don't state a range that this PR relies on. A spike with the public DHI.DFS 23.0.3 NuGet package (C#) on Linux showed:MeshFile.ReadMeshrejects a mesh with an id above 2³¹−1 ("failed reading nodes"), and so does Python'sMeshFile.ReadMesh.DfsuFileas −2147483648, with no error.GetDfsTypeinDfsFile.pyhas no 64-bit type ("Data type not supported: int64").Evidence
Claims:
eumGetNextEqvUnitlists exactly the unitseumUnitsEqvcalls equivalent to the base, ineumGetNextUnitorder, then returns(False, eumUUnitUndefined, ""). Proved bytests/test_eum.py::test_eumGetNextEqvUnit_lists_every_equivalent_unit(oracle:eumGetNextUnitfiltered byeumUnitsEqv, for meter, second, m³/s and °C),::test_eumGetNextEqvUnit_first_unitand::test_eumGetNextEqvUnit_after_last_unit.grepunder "Checks" below.DfsuBuilderwrites the node and element ids it is given (lists, int32 and int64 arrays, the int32 minimum and maximum), andNonegives the defaults 1, 2, …. Ids one past either int32 end, or beyond 64 bits, raiseValueError, and so do 2-D arrays, scalars and booleans. Oracle:DfsuFilereading the file back. Proved bytests/test_dfsu_builder.py::test_element_ids_are_written,::test_node_ids_are_written,::test_int32_bounds_are_written,::test_element_ids_set_to_none_use_the_defaults,::test_ids_outside_int32_are_rejectedand::test_ids_must_be_a_1d_integer_array.DfsuBuilder.SetFromMeshFilewrites a mesh's ids, including int64 ids fromMeshFile.Create, and a mesh with no ids gets the defaults. Proved bytests/test_dfsu_builder.py::test_ids_from_a_built_mesh_are_writtenand::test_int64_ids_from_a_created_mesh_are_written.MeshBuilder.SetElementIdsdoes the same as claim 3, withMeshFile.ReadMeshas the oracle. Proved bytests/test_mesh.py::test_MeshBuilder_element_ids_are_written,::test_MeshBuilder_element_ids_set_to_none_use_the_defaults,::test_MeshBuilder_element_ids_outside_int32_are_rejectedand::test_MeshBuilder_element_ids_must_be_a_1d_integer_array. Its default ids have the dtypeMeshFile.ReadMeshreads (the oracle), int32:::test_MeshBuilder_default_ids_are_int32_like_a_read_mesh.MeshBuilder.SetNodeswrites z exactly as given. Oracle:testdata/Oresund.meshitself.tests/test_mesh.py::test_MeshBuilder_rebuilds_the_Oresund_meshrebuilds it and compares all 2057 z values exactly, and::test_MeshBuilder_keeps_z_precisionchecks one 17-digit value.MeshBuilder.Create(dfsuFile)is unchanged:git diff origin/master...HEAD -- mikecore/MeshFile.pyis empty, andCreatebuilds throughMeshFile.Create, notSetNodes.Dfs0ToAsciistamps each row with the start time plus the item time in the axis unit, for milliseconds, seconds, minutes, hours and days, on equidistant and non-equidistant calendar axes. Oracle:datetime.timedelta. Proved bytests/test_miketools.py::test_Dfs0ToAscii_writes_calendar_times_in_the_time_unit(10 cases; the 2 in seconds also pass on master).ReadStaticItem(n)returns static item n for every n from 1 to the last, raisesValueErrorbelow 1, and returnsNonepast the end, including numbers past int32. Oracle:ReadStaticItemNext, read in reverse order so each read has to seek. Proved bytests/test_dfs_basic.py::test_read_static_item_reads_every_item_by_number,::test_read_static_item_below_one,::test_read_static_item_past_the_last_itemand::test_read_static_item_past_int32.DfsFilewithreshape=Truegives the same arrays asDfs2FileandDfs3File(the oracle). Withoutreshape, or for 0-D and 1-D items, data stays flat. Proved bytests/test_dfs_basic.py::test_generic_read_reshapes_like_dfs123,::test_generic_read_stays_flat_without_reshapeand::test_generic_read_does_not_reshape_0d_or_1d. An item with no axis is untested: no file can have one (see Deviations).SetDatadoesn't change a returned static item either. A reused dynamic builder keeps its settings. Proved bytests/test_dfsbuilder.py::test_no_builder_setter_changes_a_returned_item,::test_dynamic_item_builder_can_be_reused, the twotest_reused_*tests and::test_static_item_keeps_its_data_when_the_caller_changes_the_array.DfsuBuilderwrites Spectral0D files thatDfsuFilereads back asDfsuSpectral0D, with frequencies and directions, frequencies only, and directions only, given as arrays or lists.Validaterejects a missing spectral axis or a mismatched element count. Proved bytests/test_dfsu_builder.py::test_spectral_0d_*(including::test_spectral_0d_file_from_lists) and::test_validate_spectral_0d_*, plus the MIKE IO spike below.Dfs2FileandDfs3Filereads are unchanged. They callsuper().ReadItemTimeStepNext(itemData)withoutreshape, which the existingtests/test_dfs2.pyandtest_dfs3.pycover.The same script was run against a
git archiveoforigin/master(fe9021f) and against this branch (1c48e76; the output at 3273233 is identical), from the repo root. Each line is one bug.Before (master):
After (this branch):
The review findings on 1c48e76, with a second script (under the scripts below) run against a
git archiveof 1c48e76 and against 3273233:Before (1c48e76):
After (3273233):
Finding 3 (the hand-set axis) and finding 8 (the shared helper) change tests and structure only. Finding 7 (Spectral0D element shapes) is left open; see Deviations.
Checks on 3273233 (CI: ruff, pyrefly and tests on Ubuntu and Windows, Python 3.11 and 3.14, all pass):
Besides those 24 calls and
eumGetNextEqvUnit,eumGetItemTypeSeq(0 != iok),eumConvertUnit(iok != 0) andeumGetUnitAbbreviation(1 == ...) also treat non-zero as success.eumGetItemTypeCountandeumGetItemUnitCountreturn counts.Each test commit fails without its fix and passes with it. I checked out every test commit, then its fix commit, and ran the tests it adds:
The passing tests in a test commit are guards that also hold on master, such as the seconds case of the time units,
ReadStaticItemnumbers from 1 up, 2³¹−1 forReadStaticItem, and int64 ids fromMeshFile.Create, whichSetFromMeshFilealready cast.Spike: MIKE IO reads the Spectral0D files (Claim 11). The script writes one file per Spectral0D variant with this branch's
DfsuBuilder, then reads it with MIKE IO 3.3.0 from PyPI, with the branch first onPYTHONPATH. Every variant reads as a point spectrum with the axes, times and values written. MIKE IO uses mikecore'sDfsuFileto detect the file type, so this independently checks the geometry, axes and data, but not the type detection.MIKE IO reports directions in degrees; the file stores the radians passed to
SetDirections.Why
ReadStaticItemdoesn't check thedfsFindItemStaticreturn code (OresundHD.dfsu has 9 static items):The return code is 0 one past the end and 1002 further out, and both cases already return
None, so it doesn't tell a real error apart from past-the-end.MIKE IO downstream:
test_mikeio.yml(MIKE IOmain) ran on this branch (run 39389516723, at 3273233) and on master at the same time (run 39389522212, at fe9021f). Each job's log shows the checkout's mikecore installed. The failing tests are identical, as they were at 1c48e76 (runs 38415673442 and 38415683280):evidence.py
review_evidence.py
spectral_mikeio.py
Verify it yourself
Review
The review of 1c48e76 raised 8 findings. 7 are fixed (commits 24 to 35); finding 7, Spectral0D element shapes, is left open because no public source gives the rule (see Deviations). Finding 6 (where the static item data is copied) is fixed by moving the copy, not removing it: the stated invariant needs one copy per call.
Earlier rounds (5 rounds on 1c48e76, 3 on 933c3b2)
This run re-reviewed the whole rebased branch against the new testing rules (scope by mechanism, proved claims), with a fresh
code-reviewereach round.Round 1: CLEAN (no must-fix).
MeshBuilder.Create(dfsuFile)changed: fixed in fdbc893 (it now names float32 z passed toSetNodes, and saysCreateis unchanged).__Int32Idsbetween the builders: declined, see Deviations.OverflowError, notValueError: fixed in 6f82e0a and f201509 (tests dd5e32d and 72db4eb).Round 2: one must-fix.
DfsuBuilder.SetFromMeshFilepassed a mesh's int64 ids (fromMeshBuilder.CreateMesh()) to the writer, which failed with "Data type not supported: int64": fixed in 04707fa (test d5afdac).MeshBuilder.__elementIdstonp.ndarray | None: fixed in f201509.MeshBuilder.SetElementIdschange agreed? answered: it is a side finding under "What and why".Round 3: one must-fix, declined.
MeshBuilder.SetElementIdsis outside the plan: declined. ready-pr's rule for bugs found on the way puts a bug of a few lines, with one clear right answer and a failing test, into the PR when it is in code the PR touches. This PR already changesMeshBuilder.SetNodes, and the fix gives the mesh setter the behaviour the plan approved forDfsuBuilder's id setters. It has its own test and fix commits (72db4eb, f201509) and is named as a side finding. Round 5 raised it again only as a question.None, andreshape=Truewould then raiseAttributeError: fixed in 7b59a9e (test 602237b; removing the guard makes the test fail).SetElementIds(None)raised only afterSetElements: fixed in f201509.ValueErrorforDfs0ToAsciiwithout a time axis: declined. The plainExceptionmatches the function's other raise (see Deviations).ReadStaticItemreturn code is unchecked? answered: yes, under Evidence.Round 4: two must-fix.
eumGetNextEqvUnitreturned a unit after the last one. On master it raisesValueError: Native library returned a null string: fixed in 4d5153c.None: fixed in 7b59a9e and 602237b.Round 5: CLEAN (no must-fix).
RuntimeWarningbefore theValueError: declined. They are still rejected withValueError.Co-Authored-Byline: fixed (dd5e32d, 6f82e0a, message only).MeshBuilderscope and Spectral0D dims 1: answered as above.5 rounds run; last verdict: CLEAN, covering the head 1c48e76. After round 5, only the two commit messages changed; the tree is identical.
The earlier run's review (3 rounds, on 933c3b2)
Round 1: no must-fix.
SetElementIds(None)and the int32 wrap were fixed (now 6f82e0a, test dd5e32d).Dfs0ToAsciinow checks the time axis before opening the output file (1b82f89). The directions-only Spectral0D test was added (825d654). Answered the question on associated static items as above.Round 2: no must-fix. The id helper became a private static method, and the static builder test reuses
SetData(bee7614).Round 3: no must-fix. Declined: polishing the exception type for non-integer ids, and
ValueErrorfor the missing time axis.Risk
DfsFile.ReadItemTimeStepNextreshaping: this sits under every dfs read in mikecore and mikeio. It is safe because it only acts whenreshape=Trueand the item's axis has 2 or more dimensions, and it reads the axis shape only then.Dfs123Filecallssuper().ReadItemTimeStepNext(itemData)withoutreshape(Dfs123File.py:24), so dfs2/dfs3 behave as before. Proved bytest_dfs2.py,test_generic_read_reshapes_like_dfs123andtest_generic_read_stays_flat_without_reshape.MeshBuilder.SetNodesz precision: the text of mesh files written from float32 z passed toSetNodeschanges (-1.7859923becomes-1.7859922647476196). The values are the same float32 numbers, and the CHANGELOG says so.SetFromMeshFile: they now raiseValueErrorfor ids outside int32, for ids that are not 1-D and for booleans, and convert to int32.SetFromMeshFileraises only for a mesh with such ids, andMeshFile.ReadMeshcan't load one, so only a mesh built in memory can reach that. Ids inside int32 are written unchanged, whichtest_int32_bounds_are_writtenandtest_MeshBuilder_element_ids_are_writtenprove at both ends of the range.copy.copysharesQuantityandSpatialAxiswith the returned item. That is safe because every setter replaces attributes and none changes them in place (DfsBuilder.py:621-642), whichtest_no_builder_setter_changes_a_returned_itemchecks over every setter. The two mutable fields, the associated list and the data array, are copied. The static builder copies the data into the item it returns, once perGetStaticItem, since the builder keeps its own array for the next item and that array can be changed in place throughItemInfo.Data.AddStaticItemwrites the data to the file at once, so it keeps no copy.SetFrequenciesandSetDirectionsnow store float64, so a float32 array is written as doubles rather than floats.DfsuFilereads both, and the dfsu static items are documented as doubles inDfsuFile.MeshBuilder.CreateMesh()default ids: int32 instead of int64 on Linux. Code that compared the dtype would see a change; the values are the same.DfsuFileand MIKE IO read them as Spectral0D, but nothing here compares them with a frequency-only point spectrum written by a MIKE engine.Not in this PR
Dfs0ToAsciiwriting relative (non-calendar) time offsets in seconds: needs a check against what MIKE Zero writes.ReadItemTimeStep*().Timein seconds, andDfsuFile.GetDateTimesignoringStartTimeOffset: out of scope in the plan.ValueError: 11 is not a valid SpaceAxisType, becauseSpaceAxisTypehas no member for them: found while looking for a file for the no-axis test.DfsuBuilder.SetFromMeshFilestores a mesh'sCodeas given, so int64 codes fromMeshFile.Createfail inCreateFilewith "Data type not supported: int64".MeshFile.ReadMeshandMeshBuildergive int32.Validate(see Deviations).🤖 Generated with Claude Code