mkdocstrings / griffe

Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API.
https://mkdocstrings.github.io/griffe
ISC License
280 stars 39 forks source link

feature: API checks: Check for types compatibility #113

Open pawamoy opened 1 year ago

pawamoy commented 1 year ago

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.

pawamoy commented 5 months ago

Moving this here from the README:

pawamoy commented 5 months ago

I wonder: could we somehow recurse into objects to check that they implement the same "protocols"? Example:

class Foo:
    def method1(self): ...
    def method2(self, param1: int, param2: int): ...

class Bar:
    def method2(self, param1: int, param2: int): ...

class Baz:
    def method1(self): ...
    def method2(self, param1: int): ...

class Qux:
    def method1(self): ...
    def method2(self, param1: str, param2: int): ...

class Quux:
    def method1(self): ...
    def method2(self, param1: int, param2: int): ...
    def method3(self): ...

def f_old(param: Foo): ...

def f_new(param: Bar): ...  # breaks: removed method1
def f_new(param: Baz): ...  # breaks: removed param2 on method2
def f_new(param: Qux): ...  # breaks: changed type of param1 on method2 (int -> str is a "known" breakage)
def f_new(param: Quux): ...  # doesn't break: added method3

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.

pawamoy commented 5 months ago

Or maybe there's a way to hook into mypy...

pawamoy commented 2 weeks ago

We would need to match protocols in all expression parts.

In dict[MyStr, This] vs. MyDict[str, That]:

To compare objects, we'd have to dynamically load external objects (but only if they weren't already used). Examples:

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?