shoobx / mypy-zope Goto Github PK
View Code? Open in Web Editor NEWPlugin for mypy to support zope.interface
License: MIT License
Plugin for mypy to support zope.interface
License: MIT License
From the README:
class IAnimal(zope.interface.Interface):
def say() -> None:
pass
@zope.interface.implementer(IAnimal)
class Cow(object):
def say(self) -> None:
print("Moooo")
The interface IAnimal will be treated as an abstract base class of the implementation Cow: you will not be able to instantiate Cow if interface implementation is incomplete. Incompatible method signatures will be reported as well.
If Cow
states it implements IAnimal
, and it doesn't implement say()
, wouldn't that be an error even if Cow
is never instantiated? In other words, I think this error could reported on the class definition instead of on instantiation.
Reporting the error on the implementer's class definition would be an advantage in code bases that don't have 100% type checking coverage: if the code that instantiates the implementer is inside a function that lacks type annotations, mypy will skip that code and the incomplete interface implementation will not be reported.
I was experimenting with the latest dev release of mypy 0.970 and hit this error:
Traceback (most recent call last):
File "mypy/semanal.py", line 5350, in accept
File "mypy/nodes.py", line 1029, in accept
File "mypy/semanal.py", line 1123, in visit_class_def
File "mypy/semanal.py", line 1203, in analyze_class
File "mypy/semanal.py", line 1212, in analyze_class_body_common
File "mypy/semanal.py", line 1243, in apply_class_plugin_hooks
File "/home/arni/.config/dohop/tools_venv3/lib/python3.10/site-packages/mypy_zope/plugin.py", line 301, in analyze
apply_implementer(iface_arg, classdef_ctx.cls.info, api)
File "/home/arni/.config/dohop/tools_venv3/lib/python3.10/site-packages/mypy_zope/plugin.py", line 293, in apply_implementer
self._apply_interface(class_info, iface_type)
File "/home/arni/.config/dohop/tools_venv3/lib/python3.10/site-packages/mypy_zope/plugin.py", line 700, in _apply_interface
faketi._promote = promote
TypeError: list object expected; got mypy.types.Instance
I know you are already aware, just filing this to have something to link to :)
Thanks for making mypy-zope! We've been using it for Synapse (which uses Twisted) and ran into a hiccup.
The following will cause mypy-zope to emit an error:
error: Cannot determine consistent method resolution order (MRO) for "Foo" [misc]
from twisted.internet import protocol
class Foo(protocol.Protocol):
pass
reveal_type(Foo)
The class hierarchy looks like Foo
-> Protocol
(which implements IProtocol
and ILoggingContext
) -> BaseProtocol
.
I tried to create a reduced test-case for this, but was not able to, so it might be specific to something Twisted has done. Any help would be appreciated!
it should be possible to create a zope.interface.Interface by introspecting a typing.Protocol
class SomeProtocol(typing.Protocol):
@property
def foo(self) -> str: ...
def ham(self) -> int: ...
ISome = mypy_zope.fromProtocol("ISome", SomeProtocol)
The project foolscap relies on zope.interface
and creates a RemoteInterface as an instance of a subclass of zope.interface.interface.InterfaceClass
. When tested against mypy with this plugin, the interface isn't recognized as such an results in "Method must have at least one argument" failed validations.
As an example:
draft $ cat > mypy.ini
[mypy]
ignore_missing_imports = True
plugins=mypy_zope:plugin
draft $ cat > example.py
from foolscap.api import RemoteInterface
class Api(RemoteInterface):
def open():
"open something"
draft $ pip-run -q mypy foolscap mypy-zope -- -m mypy example.py
example.py:5: error: Method must have at least one argument
Found 1 error in 1 file (checked 1 source file)
I've attempted to create an even more minimal example that doesn't involve foolscap, but I've been unsuccessful:
draft $ cat > example.py
from zope.interface import interface
RemoteInterface = interface.InterfaceClass("RemoteInterface")
class Api(RemoteInterface):
def open():
"open something"
draft $ pip-run -q mypy mypy-zope -- -m mypy example.py
example.py:7: error: Variable "example.RemoteInterface" is not valid as a type
example.py:7: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
example.py:7: error: Invalid base class "RemoteInterface"
example.py:8: error: Method must have at least one argument
Found 3 errors in 1 file (checked 1 source file)
I don't understand why
Variable "example.RemoteInterface" is not valid as a type
The note there isn't particularly helpful, as I'm creating neither an alias nor a variable. I'm creating a proper type and in the same way that foolscap does, so why does this example error where foolscap doesn't?
And surely the example error isn't valid until mypy recogognizes RemoteInterface correctly.
Can you help to determine a solution here to support foolscap.api.RemoteInterface
subclasses within mypy?
This example doesn't complain:
from zope.interface import Interface, Attribute, implementer
class IWhatever(Interface):
stuff: str = Attribute("name")
@implementer(IWhatever)
class WhateverBroken(object):
stuff: int
even though the type of stuff
doesn't match.
This closed PR, from 2017, suggests that this syntax was intended to work one day:
zopefoundation/zope.interface#98
I'm guessing this is what the README is already talking about, when it says "Interface compatibility checker will not type-check non-method attributes"?
In twisted/klein#471, I'm running into the following error in CI:
src/klein/_dihttp.py:62:16: error: Incompatible return value type (got "RequestURL", expected "IDependencyInjector") [return-value]
return cls()
The code in question is:
@provider(IRequiredParameter, IDependencyInjector)
class RequestURL:
"""
Require a hyperlink L{DecodedURL} object from a L{Requirer}.
@since: Klein NEXT
"""
@classmethod
def registerInjector(
cls,
injectionComponents: Componentized,
parameterName: str,
requestLifecycle: IRequestLifecycle,
) -> IDependencyInjector:
return cls()
…
So here, we return a RequestURL
, which is declared as a provider of IDependencyInjector
, so I think the above error is incorrect.
I'm guessing this is due to the fact that the definition of RequestURL
isn't complete yet?
Hi,
We just ran into this stacktrace:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.800
Traceback (most recent call last):
File "mypy/semanal.py", line 4835, in accept
File "mypy/nodes.py", line 950, in accept
File "mypy/semanal.py", line 1048, in visit_class_def
File "mypy/semanal.py", line 1125, in analyze_class
File "mypy/semanal.py", line 1134, in analyze_class_body_common
File "mypy/semanal.py", line 1187, in apply_class_plugin_hooks
File "/home/travis/virtualenv/python3.8.1/lib/python3.8/site-packages/mypy_zope/plugin.py", line 266, in analyze_metaclass
if info and any(node.fullname == expected for node in info.mro):
AttributeError: 'Var' object has no attribute 'mro'
I haven't had time find exactly which piece of code is causing this, but will do so as soon as I can.
EDIT: this was tested on mypy 0.800. The line which hit this was something like this:
@attr.s(slots=True, frozen=True)
class Leg(metaclass=CustomMetaclass):
a: str = attr.ib()
b: str = attr.ib()
c: str = attr.ib()
eg I might want to define
class ICopyable(zope.interface.Interface):
def copy(self: T) -> T:
pass
but I get "Interface methods should not have 'self' argument"
Hi, and thanks for a wonderful plugin. We've started using it to check our codebase, and have stumbled upon an issue. When type-checking parts of the pyrsistent
library this happened:
/home/arni/dohop/bug/test-mypy-zope/lib/python3.7/site-packages/pyrsistent/_field_common.py:192: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.720
Traceback (most recent call last):
File "mypy/newsemanal/semanal.py", line 4550, in accept
File "mypy/nodes.py", line 921, in accept__Node_glue
File "mypy/nodes.py", line 922, in accept
File "mypy/newsemanal/semanal.py", line 989, in visit_class_def__StatementVisitor_glue
File "mypy/newsemanal/semanal.py", line 992, in visit_class_def
File "mypy/newsemanal/semanal.py", line 1063, in analyze_class
File "mypy/newsemanal/semanal.py", line 1072, in analyze_class_body_common
File "mypy/newsemanal/semanal.py", line 1131, in apply_class_plugin_hooks
File "/home/arni/dohop/bug/test-mypy-zope/lib/python3.7/site-packages/mypy_zope/plugin.py", line 287, in analyze_subinterface
base_node = api.lookup_fully_qualified_or_none(base_name)
File "mypy/newsemanal/semanal.py", line 4016, in lookup_fully_qualified_or_none
AssertionError:
/home/arni/dohop/bug/test-mypy-zope/lib/python3.7/site-packages/pyrsistent/_field_common.py:192: : note: use --pdb to drop into pdb
This happened when running under Pyhton 3.7.4, and can be reproduced like so:
#!/bin/bash
set -e
cat <<EOM >mypy.ini
[mypy]
show_traceback = True
plugins = mypy_zope:plugin
EOM
python -m venv test-mypy-zope
./test-mypy-zope/bin/pip install mypy==0.720 pyrsistent==0.15.4 mypy-zope==0.2.0
./test-mypy-zope/bin/mypy --show-traceback --config mypy.ini bug.py
Looks like this is still pinned to 073; just wanted to file this, partially to ask if anyone knows it works? :)
I'm trying to use the new types coming out of Twisted in particular the IReactorTCP and IReactorTime but having a bit of an issue with how they are declared. Here's a distilled example:
from zope.interface import implementer
from zope.interface import Interface
from zope.interface import classImplements
class IFoo(Interface):
x: int
class IBar(Interface):
y: int
def foo_it(obj: IFoo) -> int:
return obj.x
def bar_it(obj: IBar) -> int:
return obj.y
# An attempt at combining IFoo and IBar into one type.
class IFooBar(IFoo, IBar):
...
def both_it(obj: IFooBar) -> int:
assert IFooBar.providedBy(obj)
return foo_it(obj) + bar_it(obj)
@implementer(IFoo, IBar)
class FooBar:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
# This would make IFooBar.providedBy(obj) but doesn't help mypy
classImplements(FooBar, IFooBar)
obj = FooBar(6, 7)
foo_it(obj)
bar_it(obj)
both_it(obj) # ERROR: Argument 1 to "both_it" has incompatible type "FooBar"; expected "IFooBar"
So I thought, ah this is what Protocols in mypy were made for:
from typing import Protocol
class IFooBarProtoco(IFoo, IBar, Protocol):
...
Which sadly gives error: All bases of a protocol must be protocols
Is there another way to possibly declare the intersection? Or maybe the plugin can do something special with the Protocol declaration? (But is that even possible)
There's a mypy issue about it too: python/typing#21 but it doesn't provide any solutions.
I think until mypy reached 1.0.0 it was a fine policy to pin to the mypy version that was released at the time, since the plugin system is not guaranteed to be stable and the version numbering was sort of semantic, but not really, since it used number digits rather, than numbers separated by dots.
But now that mypy is following semantic versioning rules I think you should consider only pinning the mypy version to before the next minor version, so if the current version was 1.0.0, you would pin to <1.1.0, rather than ==1.0.0, so patch releases, like 1.0.1, which will often contain important bug fixes can still be installed without having to force ignore the version pin.
Hi! Thanks for this awesome project!
I am TypedDjango team member, we maintain types for, well, django. And we do pretty much the same job.
For example, we also test our types the similar way as you do in tests/
. We even created a tool called pytest-mypy-plugins
(announcing post) to help us with this task. Maybe it will be also helpful to you as well.
That's how the simplest test looks like:
- case: compose_two_wrong_functions
main: |
from returns.functions import compose
def first(num: int) -> float:
return float(num)
def second(num: float) -> str:
return str(num)
reveal_type(compose(first, second)(1)) # N: builtins.str*
Ask any questions you have!
P.S. This project was listed in awesome-python-stubs list.
currently holding back some upgrades, so filing this for tracking
Not sure if this is a mypy-zope or mypy issue.
It looks like variable vs type aliases is not supported by mypy-zope
I am looking at the folloing mypy documentation
https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
I have the following code:
class _IWaker(Interface):
"""
Interface to wake up the event loop based on the self-pipe trick.
The U{I{self-pipe trick}<http://cr.yp.to/docs/selfpipe.html>}, used to wake
up the main loop from another thread or a signal handler.
This is why we have wakeUp together with doRead
This is used by threads or signals to wake up the event loop.
"""
disconnected = Attribute('')
def wakeUp():
"""
Called when the event should be wake up.
"""
def doRead():
"""
Read some data from my connection and discard it.
"""
def connectionLost(reason: failure.Failure):
"""
Called when connection was closed and the pipes.
"""
@implementer(_IWaker)
class _SocketWaker(log.Logger):
disconnected = 0
def __init__(self, reactor):
"""Initialize.
"""
def wakeUp(self):
"""Send a byte to my connection.
"""
def doRead(self):
"""
Read some data from my connection.
"""
def connectionLost(self, reason):
class _FDWaker(log.Logger, object):
disconnected = 0
def __init__(self, reactor):
"""Initialize.
"""
def doRead(self):
"""
Read some bytes from the pipe and discard them.
"""
def connectionLost(self, reason):
"""Close both ends of my pipe.
"""
@implementer(_IWaker)
class _UnixWaker(_FDWaker):
def wakeUp(self):
"""Write one byte to the pipe, and flush it.
"""
_Waker: _IWaker
if platformType == 'posix':
_Waker = _UnixWaker
else:
# Primarily Windows and Jython.
_Waker = _SocketWaker
with mypy I got the following error:
src/twisted/internet/posixbase.py:228:14: error: Incompatible types in assignment (expression has type "Type[_UnixWaker]", variable has type "_IWaker") [assignment]
src/twisted/internet/posixbase.py:231:14: error: Incompatible types in assignment (expression has type "Type[_SocketWaker]", variable has type "_IWaker") [assignment]
I am expecting this not to raise any issue.
Thanks!
Python allows you to create new classes using the three-argument form of the type()
builtin. I can't tell if it's a mypy-zope problem or a mypy problem, but this doesn't typecheck properly:
import zope.interface
from zope.interface import Interface, Attribute, declarations
class IFoo(Interface):
x = Attribute("Some thing or other")
class NormalClass:
x = 12
declarations.classImplements(NormalClass, IFoo)
print(list(zope.interface.implementedBy(NormalClass)))
DynamicClass = type("DynamicClass", (object,), { "x": 42 })
declarations.classImplements(DynamicClass, IFoo)
print(list(zope.interface.implementedBy(DynamicClass)))
In this example NormalClass
and DynamicClass
are equivalent (except for their names and the initial value of x
). At runtime they behave similarly and the classImplements()
calls do what you'd think. Mypy however produces this error:
error: dyntest.DynamicClass is not a class, cannot mark it as a interface implementation [misc]
CI is failing, and I believe this means that this plugin doesn't support the current version of mypy. So one bug here is just that setup.py
is insufficiently restrictive as the code stands today; it needs an upper bound on mypy versions. But as I recall this is an upstream incompatibility introduced in mypy - which version does it break in, and does this require changes in mypy to start working again or is it something this plugin should change?
Tried to update to mypy 0.942, but I started seeing this:
mypy.ini:4: error: Error importing plugin "mypy_zope": cannot import name 'SemanticAnalyzerPass2' from 'mypy.semanal' (/home/runner/.cache/pre-commit/repo_fawoait/py_env-python3.8/lib/python3.8/site-packages/mypy/semanal.cpython-38-x86_64-linux-gnu.so) [misc]
I do see a green build on #68, which is a bit curious.
Is there any more info I can provide to help debug this? My current workaround it to keep mypy at 0.941.
With mypy 1.0.0 and mypy-zope 0.9.0, the typechecker seems to think that that isinstance(x, T)
is always false when T
implements some interface I
and x: Optional[I]
. For example, the source
from typing import Optional, reveal_type
from zope.interface import implementer, Interface
class IFoo(Interface):
...
@implementer(IFoo)
class MyFoo:
...
def make_foo() -> Optional[IFoo]:
return MyFoo()
x = make_foo()
reveal_type(x)
assert isinstance(x, MyFoo)
print("hello")
yields
$ mypy temp.py
temp.py:19: note: Revealed type is "Union[temp.IFoo, None]"
temp.py:21: error: Statement is unreachable [unreachable]
Found 1 error in 1 file (checked 1 source file)
but at runtime, we see that the print statement is reachable:
$ python temp.py
Runtime type is 'MyFoo'
hello
Would guess that #90 is related to this somehow.
here's a simple repro: https://github.com/graingert/amp-mypy-zope-demo/tree/default
Example:
If zope-stubs/__init__.pyi
exists, VSCode with mypy addon fails to resolve all Zope packages other than zope.interface
and zope.schema
, for example zope.component
, zope.location
, zope.site
all have no type hints.
If zope-stubs/__init__.pyi
is removed, then VSCode works with all Zope packages, and can still benefit from the stubs.
time to update again!
Probably an upstream issue, but first encountered here, so reporting the details. Running a check on a file importing zope.interface
will result in an error "Skipping analyzing zope.interface".
draft $ cat mypy.ini
[mypy]
plugins=mypy_zope:plugin
draft $ cat > interface.py
from zope.interface.interface import Interface
class X(Interface):
pass
draft $ python -m venv venv
draft $ venv/bin/pip install -q mypy-zope
draft $ venv/bin/pip uninstall -q -y mypy-zope
draft $ venv/bin/pip install -q -t libs --no-deps mypy-zope
draft $ env PYTHONPATH=libs venv/bin/python -m mypy interface.py
interface.py:1: error: Skipping analyzing 'zope.interface.interface': found module but no type hints or library stubs
interface.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
Found 1 error in 1 file (checked 1 source file)
Even though the stubs are there:
draft $ tree libs/zope-stubs/
libs/zope-stubs/
├── __init__.pyi
├── interface
│ ├── __init__.pyi
│ ├── _compat.pyi
│ ├── _flatten.pyi
│ ├── _zope_interface_coptimizations.pyi
│ ├── adapter.pyi
│ ├── advice.pyi
│ ├── common
│ │ ├── __init__.pyi
│ │ ├── idatetime.pyi
│ │ ├── interfaces.pyi
│ │ ├── mapping.pyi
│ │ └── sequence.pyi
│ ├── declarations.pyi
│ ├── document.pyi
│ ├── exceptions.pyi
│ ├── interface.pyi
│ ├── interfaces.pyi
│ ├── registry.pyi
│ ├── ro.pyi
│ └── verify.pyi
└── schema
├── __init__.pyi
├── _bootstrapfields.pyi
├── _bootstrapinterfaces.pyi
├── _compat.pyi
├── _field.pyi
├── _messageid.pyi
├── _schema.pyi
├── accessors.pyi
├── fieldproperty.pyi
├── interfaces.pyi
└── vocabulary.pyi
3 directories, 31 files
Installing the plugin directly into the virtualenv seems to work fine.
draft $ venv/bin/pip install -q mypy-zope
draft $ venv/bin/python -m mypy interface.py
Success: no issues found in 1 source file
(Maybe on your TODO list already but if not…) The zope.interface
type stubs are just what I needed! If you've got some time, it'd be great to see zope.component
get some type stubs as well.
In commit twisted/klein@04a536c, I enabled mypy-zope
for Twisted Klein and I'm getting an INTERNAL ERROR in the build.
Here is the error with --show-traceback
added:
wsanchez$ tox -e mypy -- --show-traceback src
GLOB sdist-make: /Users/wsanchez/Dropbox/Developer/Twisted/klein/setup.py
mypy inst-nodeps: /Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/.tmp/package/1/klein-20.6.0.zip
mypy installed: attrs==20.3.0,Automat==20.2.0,characteristic==14.3.0,constantly==15.1.0,hyperlink==21.0.0,idna==3.1,incremental==17.5.0,klein @ file:///Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/.tmp/package/1/klein-20.6.0.zip,mypy==0.812,mypy-extensions==0.4.3,mypy-zope==0.2.11,PyHamcrest==2.0.2,six==1.15.0,Tubes==0.2.0,Twisted==20.3.0,typed-ast==1.4.2,typing-extensions==3.7.4.3,Werkzeug==1.0.1,zope.event==4.5.0,zope.interface==5.2.0,zope.schema==6.1.0
mypy run-test-pre: PYTHONHASHSEED='3790822665'
mypy run-test: commands[0] | mypy --cache-dir=/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy_cache --pretty --show-traceback src
src/klein/test/test_trial.py:33: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.812
Traceback (most recent call last):
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/bin/mypy", line 8, in <module>
sys.exit(console_entry())
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/__main__.py", line 11, in console_entry
main(None, sys.stdout, sys.stderr)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/main.py", line 90, in main
res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 179, in build
result = _build(
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 253, in _build
graph = dispatch(sources, manager, stdout)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 2638, in dispatch
process_graph(graph, manager)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 2962, in process_graph
process_stale_scc(graph, scc, manager)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 3060, in process_stale_scc
graph[id].type_check_first_pass()
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/build.py", line 2130, in type_check_first_pass
self.type_checker().check_first_pass()
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 294, in check_first_pass
self.accept(d)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
stmt.accept(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/nodes.py", line 950, in accept
return visitor.visit_class_def(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 1723, in visit_class_def
self.accept(defn.defs)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
stmt.accept(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/nodes.py", line 1015, in accept
return visitor.visit_block(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 1972, in visit_block
self.accept(s)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
stmt.accept(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/nodes.py", line 950, in accept
return visitor.visit_class_def(self)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checker.py", line 1749, in visit_class_def
sig, _ = self.expr_checker.check_call(dec, [temp],
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checkexpr.py", line 930, in check_call
result = self.check_call(call_function, args, arg_kinds, context, arg_names,
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checkexpr.py", line 911, in check_call
return self.check_callable_call(callee, args, arg_kinds, context, arg_names,
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checkexpr.py", line 1023, in check_callable_call
new_ret_type = self.apply_function_plugin(
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy/checkexpr.py", line 732, in apply_function_plugin
return method_callback(
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy_zope/plugin.py", line 171, in analyze_implementation
iface_type = self._lookup_type(ifacename, method_ctx.api)
File "/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/lib/python3.9/site-packages/mypy_zope/plugin.py", line 583, in _lookup_type
module = api.modules[module_name]
KeyError: 'klein.test.test_trial.TestCaseTests'
src/klein/test/test_trial.py:33: : note: use --pdb to drop into pdb
ERROR: InvocationError for command /Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy/bin/mypy --cache-dir=/Users/wsanchez/Dropbox/Developer/Twisted/klein/.tox/mypy_cache --pretty --show-traceback src (exited with code 2)
__________________________________________ summary __________________________________________
ERROR: mypy: commands failed
They sort of work, in that calls to @overload
ed functions of an interface type are typechecked correctly, but there are two problems:
error: Self argument missing for a non-static method (or an invalid type for self)
error: Signature of "MySomething" incompatible with "getStuff" of supertype "ISomething"
.Here's a gist demonstrating the situation I'm talking about.
Interestingly, the actual uses of both the interface and the concrete class seem to be typechecked correctly at least some of the time: the assignments to variables i
and j
are satisfactory to mypy, even though they wouldn't be without the @overload
s.
(Using mypy==0.812 mypy-extensions==0.4.3 mypy-zope==0.2.13 )
I think I may have stumbled onto another defect in mypy.zope
. Consider this code:
from zope.interface import implementer
from twisted.cred.credentials import ICredentials
class IToken(ICredentials):
pass
@implementer(IToken)
class Token:
pass
Run that through mypy with this mypy.ini:
[mypy]
ignore_missing_imports = True
plugins=mypy_zope:plugin
With twisted and zope interface installed, and this error occurs:
$ .tox/typechecks/bin/python -m mypy --no-incremental creds.py
creds.py:9: error: zope.interface.implementer accepts interface, not creds.IToken.
creds.py:9: error: Make sure you have stubs for all packages that provide interfaces for creds.IToken class hierarchy.
Found 2 errors in 1 file (checked 1 source file)
But make either of these tweaks and the errors go away:
from zope.interface import implementer
from twisted.cred.credentials import ICredentials
class IToken(ICredentials):
pass
_impl = implementer(IToken)
@_impl
class Token:
pass
from zope.interface import implementer, Interface
class ICredentials(Interface):
pass
class IToken(ICredentials):
pass
@implementer(IToken)
class Token:
pass
Hi. With the latest version of mypy and mypy-zope we're getting mypy errors that I think are cache related. The error goes away after rm -rf .mypy_cache
Here's the smallest repro I could make:
src/zg/foo.py:
from zg.bar import ZGScriptProtocol
print(ZGScriptProtocol.transport)
src/zg/bar.py:
from twisted.internet.protocol import ProcessProtocol
class ZGScriptProtocol(ProcessProtocol):
pass
I am able to reliably hit the problem by running the following commands:
$ rm -rf .mypy_cache
$ mypy src/zg/foo.py
Success: no issues found in 1 source file
$ mypy src/zg/foo.py src/zg/bar.py
src/zg/bar.py:3: error: Cannot determine consistent method resolution order (MRO) for "ZGScriptProtocol"
src/zg/foo.py:3: error: "Type[ZGScriptProtocol]" has no attribute "transport"
$ mypy --version
mypy 0.971 (compiled: yes)
mypy-zope==0.3.9
and twisted[tls]==22.4.0
This probably requires some implementation support from the zope.interface side, but I find myself wanting to be able to write this:
from dataclasses import dataclass
from typing import Generic, TypeVar
from zope.interface import Interface, implementer
T = TypeVar("T")
class IA(Generic[T], Interface):
def m() -> T:
...
@implementer(IA)
@dataclass
class A(Generic[T]):
_v: T
def m(self) -> T:
return self._v
a: IA[int] = A(3)
This actually appears to do what I want at type-check time! But then it (somewhat obviously) crashes at runtime.
This horrible hack almost works though, which suggests that this could work properly with some very small changes:
# from __future__ import annotations
from dataclasses import dataclass
from typing import Generic, TypeVar, TYPE_CHECKING
from zope.interface import Interface, implementer
T = TypeVar("T")
if TYPE_CHECKING:
from typing import Generic as GenericInterface
else:
from zope.interface.interface import InterfaceClass
class SpecialInterfaceClass(InterfaceClass):
def __getitem__(self, key):
return self
EmptyInterface = SpecialInterfaceClass("<Empty>", __module__=__name__)
class GenericInterfaceClass:
def __getitem__(self, typevars):
return EmptyInterface
GenericInterface = GenericInterfaceClass()
class IA(Interface, GenericInterface[T]):
def m() -> T:
...
@implementer(IA)
@dataclass
class A(Generic[T]):
_v: T
def m(self) -> T:
return self._v
@dataclass
class B:
...
a: IA[int] = A(3) # works, hooray
a = B() # error, hooray
a = A("oops") # no error, boo
When checking the following modified example:
class IAnimal(zope.interface.Interface):
def say(self) -> None:
pass
The following error message is output:
error: Name 'self' already defined (possibly by an import)
While the plugin is correct in flagging this use of self
as an error, the message is a bit cryptic. It would be nice to have a specific error message for this problem: I imagine I'm not the only one making this mistake in their code.
Hi. I was testing the upcoming mypy release and it seems to be emitting new warning for interface definitions:
import zope.interface
class IZomething(zope.interface.Interface):
def Query(a: str, b: str) -> list[int]:
pass
gives:
$ mypy --config=mypy.ini int.py
int.py:5: error: Missing return statement [empty-body]
Found 1 error in 1 file (checked 1 source file)
Replacing the function body with ellipses silences the warning. Perhaps that should be the preferred way to define interfaces moving forward?
Hi,
First of all, thank you for this amazing library. I'm using this library on some projects with extensive use of getUtility
and getAdapter
, and I was expecting that these functions would have as a return type the interface of provides
argument. Right now we need to do add the interface explicitly u: IMyUtility = getUtility(IMyUtility)
to get type validation.
from zope.interface import Interface
from zope.interface import implementer
from zope.component import getUtility
class IMyUtility(Interface):
def foo(x: int):
pass
@implementer(IMyUtility)
class MyUtility():
def foo(self, x):
return 2 * x
u = getUtility(IMyUtility)
# Mypy doesn't produce any error
u.foo("2")
u2: IMyUtility = getUtility(IMyUtility)
# Mypy validation works and provides this error:
# error: Argument 1 to "foo" of "IMyUtility" has incompatible type "str"; expected "int"
u2.foo("2")
I tried adding def getUtility(self, provided: T, name: str = ...) -> T: ...
(https://github.com/Shoobx/mypy-zope/blob/master/src/zope-stubs/interface/registry.pyi#L27) but I'm getting Type[IMyUtility]
instead of IMyUtility
.
Any idea on how to fix it? Does it make sense to add this to mypy-zope
?
Thank you!
i.e.
from zope.interface import Interface
class IFoo(Interface):
def foo() -> int:
...
reveal_type(IFoo(object(), None))
Revealed type is "IFoo"
This ought to be IFoo | None
.
When checking the following code:
from twisted.internet import interfaces
from zope.interface import implementer
@implementer(interfaces.IPushProducer)
class MyProducer:
pass
This error is reported:
error: Interface should be specified (should never happen)
It is possible to work around this by importing IPushProducer
directly instead of importing its module.
Hello,
Thanks for your work on adding typing on zope.interface. Although we haven't used your project directly, it's been a lot of help.
We've been trying to generate stubs for zope.interface, but we're having problems generating one for C extension _zope_interface_coptimizations.c
, with the following traceback:
Traceback (most recent call last):
File "/home/user/tmp/_env/bin/stubgen", line 10, in <module>
sys.exit(main())
File "mypy/stubgen.py", line 1173, in main
File "mypy/stubgen.py", line 1084, in generate_stubs
File "mypy/stubgenc.py", line 51, in generate_stub_for_c_module
File "mypy/stubgenc.py", line 268, in generate_c_type_stub
File "mypy/stubgenc.py", line 153, in generate_c_function_stub
TypeError: str object expected
It's crashing trying to generate from docstring, we were wondering how you were able to generate the stub file.
Thanks again for taking time to read this.
In setup.cfg
at HEAD
it looks like this version is already supported, so a PyPI release would be nice. (I keep bumping into projects where I can't upgrade yet.)
I'd like to include mypy-zope in my project, but if I roll back mypy from 0.670 to 0.660 I would get a bunch of false positives. I know I can install the current development state directly from GitHub with pip, but I'm using Poetry to maintain virtualenvs for multiple Python versions and installing packages directly with pip means I'd have to manually do that for every virtualenv, plus I have to manually update them later as well.
So it would really help me if there was a mypy-zope release on PyPI that depends on mypy >= 0.660.
currently only sdists are available.
python -m pip install -U build twine
python -m build --wheel --sdist .
python -m twine upload dist/*
should do the right thing when you next deploy
Looks like stubs were generated for an old version of zope.interface, is that intended?
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 C
s, 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?
To give a concrete example: Twisted's HostnameEndpoint.__init__
expects a reactor
argument which should be
provider of
IReactorTCP
,IReactorTime
and eitherIReactorPluggableNameResolver
orIReactorPluggableResolver
.
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!
Hi. With the latest version of mypy (0.991) and mypy-zope (0.3.11) we're getting mypy errors that I think are cache related. The error goes away after rm -rf .mypy_cache
Here's the easiest repro I could make:
iface.py
from zope.interface import Interface
from zope.interface import implementer
class ISomething(Interface):
pass
class ISomething2(Interface):
pass
@implementer(ISomething)
class OneInterface:
pass
@implementer(ISomething, ISomething2)
class TwoInterfaces:
pass
foo.py
from iface import OneInterface, TwoInterfaces
class Good(OneInterface):
pass
class Bad(TwoInterfaces):
pass
I am able to reliably hit the problem by running the following commands:
$ rm -rf .mypy_cache
$ mypy iface.py
Success: no issues found in 1 source file
$ mypy foo.py
foo.py:6: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [misc]
Found 1 error in 1 file (checked 1 source file)
As you can tell this only happens when there are multiple interfaces.
Hi,
I ran across an interesting issue. The signature of open()
unexpectedly became typing.IO[Any]
instead of typing.TextIO
when the plugin is enabled.
example.py
:
reveal_type(open('str'))
plugin.ini
:
[mypy]
plugins = mypy_zope:plugin
Here is small test:
$ more example.py
reveal_type(open('str'))
$ more plugin.ini
[mypy]
plugins = mypy_zope:plugin
$ mypy --config plugin.ini example.py
example.py:1: note: Revealed type is 'typing.IO[Any]'
$ mypy example.py
example.py:1: note: Revealed type is 'typing.TextIO'
I created a test-case here:
Here's an example:
from collections import Iterator
from contextlib import contextmanager
from typing import TypeVar, Generic
_T = TypeVar("_T")
class A(Generic[_T]):
pass
@contextmanager
def m(x: _T) -> Iterator[A[_T]]:
...
with m(7) as x:
reveal_type(x)
print(x)
Without the plugin you get: note: Revealed type is 'bar.A*[builtins.int*]'
With the plugin you get: Revealed type is 'bar.A*[_T
-1]'`
I tracked the issue down to get_function_hook
doing a return analyze
for all methods thus preventing the hooks at mypy.plugins.default.DefaultPlugin.get_function_hook
from running.
This is the same as #16
Looking at python/mypy#8331 I tried figuring out how one could use lookup_fully_qualified
and here's some code that works for some cases.
info = self.lookup_fully_qualified(fullname)
if (
info and info.node and isinstance(info.node, SYMBOL_FUNCBASE_TYPES)
and info.node.type and isinstance(info.node.type, CallableType)
and info.node.type.ret_type and isinstance(info.node.type.ret_type, Instance)
):
info.node = info.node.type.ret_type.type
if (info
and info.node
and isinstance(info.node, TypeInfo)
and info.node.mro
and any(typeinfo.fullname in {"zope.interface.interface.Attribute", "zope.schema.fieldproperty.FieldProperty"}
for typeinfo in self.lookup_fully_qualified(fullname).node.mro)):
return analyze
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.