Skip to content

refactor(checks)!: drop LesserThan and LesserThanEquals classes - #2720

Merged
kevinmessiaen merged 6 commits into
mainfrom
feature/eng-1677-drop-lesserthan-and-lesserthanequals-checks
Aug 12, 2026
Merged

refactor(checks)!: drop LesserThan and LesserThanEquals classes#2720
kevinmessiaen merged 6 commits into
mainfrom
feature/eng-1677-drop-lesserthan-and-lesserthanequals-checks

Conversation

@kevinmessiaen

Copy link
Copy Markdown
Member

Closes ENG-1677

What

The Giskard Hub v3 now emits the correct LessThan / LessThanEquals names, so the misspelled alias classes have no remaining producer. This removes them from the public API while keeping their serialized kind strings loadable.

Why not a hard drop

LesserThan / LesserThanEquals were not just Python aliases — they were registered in the discriminated-union registry via @Check.register("lesser_than"). Deleting them outright would make any check JSON persisted before the Hub upgrade fail to deserialize with a hard ValidationError, not a degraded read.

So the classes are gone, but the legacy kinds still load:

@Check.register("less_than", aliases=["lesser_than"])
class LessThan(...): ...

aliases= registers extra discriminators on the read path only. Only the canonical kind is mapped back from the class, so a legacy payload loads as a real LessThan and is re-saved as less_than — migrating itself on the next write.

This lives in the shared Discriminated registry rather than locally in giskard-checks because __get_pydantic_core_schema__ installs a plain validator that replaces the model schema. Validation dispatches to the concrete class before any Check-level model_validator could rewrite kind, so a downstream fix would have required monkeypatching registry internals.

Registry fixes found while in there

  • Atomic registration. Validating and writing in a single pass left the registry half-populated when a later alias collided — a class whose decorator raised stayed loadable. Now validate-then-write.
  • Dropped _reverse_kinds, which held data byte-identical to _subclasses (verified across all 7 registered bases). Reading _subclasses via .get() also stops the former defaultdict from inserting an empty entry on each failed lookup.
  • Reject a bare str for aliases. aliases="lesser_than" would silently register one kind per character; str satisfies Sequence[str], so no type checker catches it. aliases is now keyword-only.

Breaking change

LesserThan and LesserThanEquals are no longer importable from giskard.checks — use LessThan / LessThanEquals. Stored payloads using the legacy kind strings are unaffected. Worth a release-note line for OSS 3.0.

Testing

  • giskard-core 47 passed, giskard-checks 804 passed / 4 skipped
  • make check exit 0 (basedpyright 0 errors, pip-audit + licensecheck clean)
  • The atomicity test was verified to actually catch the bug — it fails with DID NOT RAISE against the single-pass version
  • giskard-agents has 7 pre-existing failures from a missing Gemini API key; confirmed identical on a clean tree

Follow-up

Legacy alias hits are not observable: _telemetry_props.py reads kind off the instance, which now always returns the canonical name. Without a counter or debug log at the alias-match site, these aliases can never be retired on evidence. Worth a separate ticket.

🤖 Generated with Claude Code

The Giskard Hub v3 now emits the correct `LessThan` / `LessThanEquals`
names, so the misspelled alias classes have no remaining producer and are
removed from the public API.

Their serialized `kind` strings are kept loadable so suites persisted
before the Hub upgrade still deserialize. This is done via a new
`aliases=` parameter on `Discriminated.register()`, which registers extra
discriminators on the read path only:

    @Check.register("less_than", aliases=["lesser_than"])

Only the canonical kind is mapped back from the class, so a legacy payload
loads as `LessThan` and is re-saved as `less_than` — migrating itself on
the next write.

While in the registry:
- Register atomically. Validating and writing in a single pass left the
  registry half-populated when a later alias collided, so a class whose
  decorator raised stayed loadable.
- Drop `_reverse_kinds`, which held data identical to `_subclasses`.
  Reading `_subclasses` via `.get()` also stops the former defaultdict
  from inserting an empty entry on every failed lookup.
- Reject a bare `str` for `aliases`, which would otherwise register one
  kind per character. `str` satisfies `Sequence[str]`, so only a runtime
  guard catches it.

BREAKING CHANGE: `LesserThan` and `LesserThanEquals` are no longer
importable from `giskard.checks`. Use `LessThan` and `LessThanEquals`.
Stored payloads using the legacy `kind` strings are unaffected.

Closes ENG-1677

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

ENG-1677

Reuse one kinds tuple for validate-then-write, avoid allocating an empty
lookup dict on unknown bases, and type the non-string alias test for
basedpyright.

Co-authored-by: Kevin Messiaen <kevinmessiaen@users.noreply.github.com>
Legacy lesser_than / lesser_than_equals discriminators are no longer
accepted on deserialize. Remove Discriminated.register(aliases=), which
only existed for that migration path, and the tests that asserted legacy
kind round-trips.

@henchaves henchaves left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only need to update method names in test_comparison.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are many lesser_than in method names within this file

kevinmessiaen and others added 2 commits August 12, 2026 14:17
Align test method names in test_comparison.py with the LessThan and
LessThanEquals class names and their less_than / less_than_equals
serialised kinds, removing the last references to the dropped Lesser*
vocabulary.

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

Development

Successfully merging this pull request may close these issues.

3 participants