Add ArrayConverter for array payload members - #39
Open
glopesdev wants to merge 1 commit into
Open
Conversation
A payload member spanning several elements had no expressible type. The sub-array dtype IdentityConverter needs carries np.void as its scalar type, so the member resolved as void, and pinning the type parameter instead resolves it as the scalar. ArrayConverter takes the element type and a length and resolves as an NDArray of that element. It is typing-only and builds an IdentityConverter over the same sub-array dtype, so the runtime object is the passthrough converter every code path and isinstance already expects. The runtime emitter uses it for a member longer than one element, so a schema-built module and a generated package agree. harp-benchmarks was the one package src tree outside the pyright include, and no test imports its benchmark module, so three parse_to_dataframe calls still passed timestamp and raised TypeError. They now pass time_index, a masked np.int32 member no longer takes a Python int, and the StartPulseTrain default check supplies the two members that dataclass_transform makes required.
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.
A payload member spanning several elements of one type could not be given an honest annotation.
ArrayConvertercloses that, and the harp-benchmarks package that exercised the gap is brought under pyright so the next drift is caught by CI.The member that could not be typed
A
payloadSpecmember declaring its ownlengthis emitted as a passthrough over a sub-array dtype:pyright rejects the assignment with
Type "void" is not assignable to declared type "NDArray[float32]", because numpy types a sub-array dtype asdtype[void], so the converter type parameter binds tonp.void.Both obvious repairs were measured on a probe and both fail:
IdentityConverter[np.float32](np.dtype((np.float32, (3,)))), pinning the parameter explicitly, yieldsField[np.float32], and a scalar is not assignable to an array eithernp.dtype[np.float32]before passing it does the same thing for the same reasonSo the element type and the length have to arrive as separate arguments, because a sub-array dtype has already discarded the element type by the time any overload could inspect it.
ArrayConverter(np.float32, 3)does that and resolves asNDArray[np.float32].Nothing about
Fieldchanges.Converteris generic in its decoded type with no bound, anddecode_batchalready returnsAny, so an array-valued converter was expressible all along.StringConverteris the existing precedent: it takes a length, builds the identical sub-array dtype, decodes to a non-scalar, and appears in generated output today ascore_id: str = Field(StringConverter(3), offset=9).Why it routes through IdentityConverter at runtime
ArrayConverteris declared underTYPE_CHECKINGand is a factory outside it, returning anIdentityConverterover the equivalent sub-array dtype. The runtime object is therefore the passthrough converter every existing code path already expects, and_payload.pyhas a zero-line delta.That matters more than it looks. Sub-array column expansion, the step that renders
accelerometerasaccelerometer_0,accelerometer_1andaccelerometer_2, is selected by anisinstancecheck againstIdentityConverter. A genuinely separate converter class fails that check, takes the whole-element branch, and hands pandas a 2-D column, which raisesValueError: Per-column arrays must each be 1-dimensional. Routing to the same runtime class avoids introducing that divergence at all rather than teaching each site about a second converter.Measured after the change: the constructed object is an
IdentityConverter, its dtype equals the direct spelling at itemsize 12, all fourteen registers round-trip, andharp-benchmark --headstill produces six columns forAnalogData.The cost is that
isinstance(x, ArrayConverter)raises outside type checking, and the declared signature has to be kept in step with the factory by hand. Nothing in the repository does either, and the two sit adjacent in the same module.The runtime emitter now uses it for a member longer than one element, so a module built from a schema and a generated package resolve the same member the same way.
Checking harp-benchmarks
harp-benchmarks/srcwas the one packagesrctree absent from the pyrightinclude, and no test imports its benchmark module, onlyregister_models. Threeparse_to_dataframecalls would still have passedtimestampand raisedTypeErrorat runtime; they now passtime_index. Two further errors in that package were real rather than noise: a maskednp.int32member was given a Pythonint, and theStartPulseTraindefault check omitted two members that@dataclass_transformmakes required. Adding the tree to the include is what catches the next rename.The
tests/conformance.pyfixture enforces the typed contract. If a change causes an array member to revert to a scalar value without warning, the build process will fail.What this does not change
tests/device/expected_device.pykeeps its two array errors, because it is a byte copy of generator output and resyncs by copying rather than by hand. It sits outside the pyrightinclude, so CI is unaffected either way. Those clear once the Python generator target emitsArrayConverter, which is the follow-up this unblocks and the point at which real device packages benefit.