Python semantic layer: Injected marker and the @measure decorator - #247
Open
jat255 wants to merge 6 commits into
Open
Python semantic layer: Injected marker and the @measure decorator#247jat255 wants to merge 6 commits into
jat255 wants to merge 6 commits into
Conversation
…otated_attribute Replaced deprecated pydantic API with the supported path for building FieldInfo from annotations with defaults. This also merges all field constraints (e.g. Field(gt=0)) rather than silently dropping them. Added test to verify constraint merging.
FieldInfo.from_annotated_attribute overwrites a Field's default= with PydanticUndefined whenever the signature omits its own default, silently making the parameter required in the schema while Python's call convention still requires it. Raise TypeError at decoration time instead, naming the parameter and telling the author to move the default into the signature.
Route direct as_measure() dereferences through a shared _as_measure() helper so pyrefly sees Measure instead of Measure | None.
The fail-closed check in 75914f5 only inspected the FieldInfo that _described_field() returns, the one carrying the description. An annotation can carry more than one Field(...), and a default declared in a separate one slipped through, producing a required schema field whose default Python never applies. Scan all FieldInfo metadata on the annotation instead of just the described one.
jat255
force-pushed
the
jat255/wwmt-measure-basics
branch
from
September 2, 2026 03:14
064a319 to
52af05b
Compare
jat255
marked this pull request as ready for review
September 2, 2026 04:00
jat255
force-pushed
the
jat255/wwmt-measure-basics
branch
from
September 2, 2026 04:02
52af05b to
064a319
Compare
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.
First of four stacked PRs building the Python semantic layer (M3). This one adds the measure record and the rule that tells the two kinds of measure argument apart.
These are implemented in
pkg-py/src/commons/_measures.pyand corresponding tests.Two types of measures:
Annotated[T, Field(description=...)]Injected[T]and they never reach the model.A parameter that is neither raises
TypeErrorat decoration time. This came from decision D9, and it is stricter than the R package, where a forgotten@paramsilently hides an argument from the model.Some API notes
@measurereturns the function unchanged and attaches the record as an attribute, so measures and the helpers they call stay ordinary callables.The schema is not built through chatlas, because
Tool.from_func()rejects any model whose fields do not cover every function parameter, which won't work on injected parameters. Measures are never registered with the provider in any case, since the provider sees onlycall_measure.Argument validation uses
pydantic'smodel_validateon anextra="forbid"model. That covers unknown arguments, required arguments, enum vocabularies, and coercion in one call, so there is no need for a hand-written validator. Its error text differs from the R package's, which is accepted because the text goes back to the model as a tool error rather than to a user.One caveat worth noting: An
AnnotatedFieldcarryingdefault=ordefault_factory=is a decoration error, and the default belongs in the signature instead.For example, the following will not work:
validate_argsomits unset arguments so that Python applies the default at call time. Accepting aFielddefault would make the schema optional while the signature still required the argument, and the call would then fail mid-conversation.Not in this PR:
semantic_layer(), injection resolution, and the public exports. Those are the PRs above it in the stack.