Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to express "A type which implements these N interfaces" #58

Open
DMRobertson opened this issue Oct 28, 2021 · 3 comments
Open

A way to express "A type which implements these N interfaces" #58

DMRobertson opened this issue Oct 28, 2021 · 3 comments

Comments

@DMRobertson
Copy link
Contributor

Let's say I have two interfaces IA and IB. They're completely independent of each other. I implement both in some type C.

from zope.interface import Interface, implementer

class IA(Interface):
    def do_a(): ...

class IB(Interface):
    def do_b(): ...


@implementer(IA, IB)
class C:
    def do_a(): ...
    def do_b(): ...

I can assign a C instance to a variable marked against either interface.

a: IA = C  # ok
b: IB = C  # ok

I can also pass a C instance to a function requiring either interface.

def needs_IA(a: IA): ...
def needs_IB(b: IB): ...

needs_IA(c)  # ok
needs_IB(c)  # ok

Suppose I have another function which requires both interfaces.

def needs_IA_and_IB(arg): ...
needs_IA_and_IB(c)         # want this to be checked as ok
needs_IA_and_IB(object())  # want this to be checked as not okay

If I know that I'm only going to use Cs, I can mark arg: C and call it a day.
But this doesn't work if I have a second type D that implements both interfaces.

@implementer(IA, IB)
class D:
    def do_a(): ...
    def do_b(): ...
    
    
d = D()
needs_IA_and_IB(d)  # want this to also be okay

How should I annotate arg? What I'd like is a way to describe "a type that implements each of these interfaces". Is there a way to do this at present? There's no notion of an intersection type; maybe there'd need to be a new Type spelt Implements[IA, IB, ...] for this purpose?

Example

To give a concrete example: Twisted's HostnameEndpoint.__init__ expects a reactor argument which should be

provider of IReactorTCP, IReactorTime and either IReactorPluggableNameResolver or IReactorPluggableResolver.

I don't think that can be currently expressed in the type system. I'd want to spell it as something like

Implements[
    IReactorTCP,
    IReactorTime,
    Union[IReactorPluggableNameResolver, IReactorPluggableResolver]
]

The inner Union might make things harder; even being able to express one of the two choices like

Implements[IReactorTCP, IReactorTime, IReactorPluggableNameResolver]

would be helpful!


I don't know if this is something in the remit of mypy-zope as opposed to the zope project itself. I'm hopingthis is a good to ask fo suggestions or feedback though!

@kedder
Copy link
Member

kedder commented Oct 28, 2021

How should I annotate arg?

I think this is a question of broader scope than zope interfaces. In a pure python context, one could ask "how do I annotate a variable that is of a class Horse but also has wings property?" No languages I know that would have a syntax for that directly, but usual approach here is to define the compound type using inheitance for example.

How about defining a new interface that would inherit both IA and IB and then requiring IAB in your function?, e.g.

class IAB(IA, IB):
    ...

def needs_IA_and_IB(c: IAB):
    ...

@DMRobertson
Copy link
Contributor Author

How about defining a new interface that would inherit both IA and IB and then requiring IAB in your function?, e.g.

This doesn't seem to work for me. Here are the full details, but in short:

example.py:59: error: Argument 1 to "needs_IA_and_IB" has incompatible type "C"; expected "IAB"  [arg-type]
example.py:60: error: Argument 1 to "needs_IA_and_IB" has incompatible type "D"; expected "IAB"  [arg-type]
example.py:61: error: Argument 1 to "needs_IA_and_IB" has incompatible type "object"; expected "IAB"  [arg-type]

If I declare IAB early and mark C and D as @implementer(IAB) then this all works perfectly. (Perhaps that's what you had in mind?) That's a bit more tricky for my situation. I'm trying to write a stub for a part of twisted. I can add a compound type like IAB to the stub files, but I can't consume such a type from the application (because it's not really a part of twisted).

I appreciate that's a convoluted and niche scenario!

@euresti
Copy link
Contributor

euresti commented Dec 22, 2021

You can use classImplements like this:

from zope.interface import classImplements
classImplements(C, IAB)
classImplements(D, IAB)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants