You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feature: API checks: Check for types compatibility #113
Following the discussion in #75, we released a first version of the API breakage detection feature, which does not check if types (parameters types, return types) are compatible between the old and new code.
This issue is here to discuss about adding support for checking types compatibility.
It's already possible at runtime with beartype, so maybe we could use in the inspector. But for the visitor we need something static.
Reacted by Edgar Ramírez Mondragón and Aaron Abbott
All of the previous even when parent is private (could be publicly imported or assigned somewhere),
and later be smarter: public assign/import makes private things public!
Inheritance: removed, added or changed base that changes MRO
I imagine a type_compatible(self, other) method on objects that uses next(find_breaking_changes(self, other)) and returns False if a breakage is yielded, True if StopIteration is raised.
We would need to match protocols in all expression parts.
In dict[MyStr, This] vs. MyDict[str, That]:
dict and MyDict must match
MyStr and str must match
This and That must match
To compare objects, we'd have to dynamically load external objects (but only if they weren't already used). Examples:
param: list[otherpkg.Thing] vs. param: Sequence[otherpkg.Thing]: here we see that otherpkg.Thing is the same object (canonical path) before and after, so we don't need to load it
param: otherpkg.Thing vs. param: set[str]: here we'd need to load otherpkg.Thing
param: set[str] vs. param: otherpkg.Thing: here we'd need to load otherpkg.Thing again
Or we could expect users to pre-load these modules and soft-fail otherwise.
By the way, how to compare set[str] to Thing? Should Thing inherit from a generic with str type? Or should all the equivalent parameters be typed str, and if they're typed T (type alias? how to check that?) then only check generics?
Perhaps we could use pycroscope? I've asked Jelle if he thinks it's possible.
pycroscope exposes some APIs and is a semi-static type checker (it reasons about runtime objects), so I think it's potentially just a matter of making griffe and pycroscope talk to each other.
As far as I remember, pycroscope has some limitations in type checker-specific things like type narrowing on control flow, but for our base use case it sounds terrifically good.
It's a nice approach, just really slow and won't detect some things that would break outside of packages checked by primer (and primer does check loooots of packages).
By the way, how to compare set[str] to Thing? Should Thing inherit from a generic with str type? Or should all the equivalent parameters be typed str, and if they're typed T (type alias? how to check that?) then only check generics?
Note that we'd also have to check variance to know what should be assignable to what.
For example, foo() in all versions (before and after change) passes local type checking, but only the first change is compatible with 3rd party dependers:
Hm, I think I got confused. It's kind of unrealistic to do it with pycroscope when I think about it. pycroscope would need to import both packages in the same process, as Jelle noted.
Activity
pawamoy commentedon Mar 2, 2024
Moving this here from the README:
and later be smarter: public assign/import makes private things public!
[-]API breakages: Check for types compatibility[/-][+]feature: API checks: Check for types compatibility[/+]pawamoy commentedon Mar 2, 2024
I wonder: could we somehow recurse into objects to check that they implement the same "protocols"? Example:
I imagine a
type_compatible(self, other)method on objects that usesnext(find_breaking_changes(self, other))and returns False if a breakage is yielded, True if StopIteration is raised.pawamoy commentedon Mar 2, 2024
Or maybe there's a way to hook into mypy...
pawamoy commentedon Aug 11, 2024
We would need to match protocols in all expression parts.
In
dict[MyStr, This]vs.MyDict[str, That]:dictandMyDictmust matchMyStrandstrmust matchThisandThatmust matchTo compare objects, we'd have to dynamically load external objects (but only if they weren't already used). Examples:
param: list[otherpkg.Thing]vs.param: Sequence[otherpkg.Thing]: here we see thatotherpkg.Thingis the same object (canonical path) before and after, so we don't need to load itparam: otherpkg.Thingvs.param: set[str]: here we'd need to loadotherpkg.Thingparam: set[str]vs.param: otherpkg.Thing: here we'd need to loadotherpkg.ThingagainOr we could expect users to pre-load these modules and soft-fail otherwise.
By the way, how to compare
set[str]toThing? ShouldThinginherit from a generic withstrtype? Or should all the equivalent parameters be typedstr, and if they're typedT(type alias? how to check that?) then only check generics?johnslavik commentedon Sep 17, 2025
Perhaps we could use pycroscope? I've asked Jelle if he thinks it's possible.
pycroscope exposes some APIs and is a semi-static type checker (it reasons about runtime objects), so I think it's potentially just a matter of making griffe and pycroscope talk to each other.
As far as I remember, pycroscope has some limitations in type checker-specific things like type narrowing on control flow, but for our base use case it sounds terrifically good.
johnslavik commentedon Sep 17, 2025
FYI, folks from typeshed kinda solved this with mypy_primer using heuristics. It generates nice comments like this:
__exit__return types of generator context managers specifiable python/typeshed#14439 (comment) (breaking)It's a nice approach, just really slow and won't detect some things that would break outside of packages checked by primer (and primer does check loooots of packages).
johnslavik commentedon Sep 17, 2025
That's a complicated topic. We'd need to implement consistency and materialization to decide about assignability.
Note that we'd also have to check variance to know what should be assignable to what.
For example,
foo()in all versions (before and after change) passes local type checking, but only the first change is compatible with 3rd party dependers:this one isn't:
because callables are contravariant on the argument type and covariant on the return type, as nicely explained in https://peps.python.org/pep-0483/#covariance-and-contravariance.
pawamoy commentedon Sep 17, 2025
Maybe I'm missing something but:
...sounds like a no-go to me for static analysis in Griffe 🤔
johnslavik commentedon Sep 17, 2025
Hm, I think I got confused. It's kind of unrealistic to do it with pycroscope when I think about it. pycroscope would need to import both packages in the same process, as Jelle noted.