-
Notifications
You must be signed in to change notification settings - Fork 5
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
Story sub-objects not being type-checked by mypy??? #233
Comments
NOTE! we use "pickle" to transmit Stories in queue messages. Any change to internal format MUST preserve the ability to process already queued messages (for example, ones trapped in quarantine queues), or at least any deployment of a breaking change needs to be done carefully (emptying all queues first)!!!!! |
Appears to be a mypy bug, not related to dataclasses either:
|
@kilemensi since I investigated due to your review comment: I asked on a python typing chat (before opening a mypy issue) and got the answer that the
I'm not quite sure how to fix this short of having each |
also @pgulley since you created the Story class framework |
Can't think of any other way out either @philbudne. Being on |
Found this, but didn't try anything out: python/mypy#3839 |
Looks like from typing import TypeVar
TFoo = TypeVar("TFoo", bound="Foo")
class Foo:
a: int
def __enter__(self: TFoo) -> TFoo:
return self
def __exit__(self: TFoo, *args) -> None:
pass
class Bar(Foo):
pass
class Baz(Foo):
b: int
# Foo
f1 = Foo()
with f1:
reveal_type(f1)
f1.a = 1
f1.b = 2
with Foo() as f2:
reveal_type(f2)
f2.a = 1
f2.b = 2
def f() -> Foo:
return Foo()
with f() as f3:
reveal_type(f3)
f3.a = 1
f3.b = 2
# Bar
r1 = Bar()
with r1:
reveal_type(r1)
r1.a = 1
r1.b = 2
with Bar() as r2:
reveal_type(r2)
r2.a = 1
r2.b = 2
def r() -> Bar:
return Bar()
with r() as r3:
reveal_type(r3)
r3.a = 1
r3.b = 2
# Baz
z1 = Baz()
with z1:
reveal_type(z1)
z1.a = 1
z1.b = 2
with Baz() as z2:
reveal_type(z2)
z2.a = 1
z2.b = 2
def z() -> Baz:
return Baz()
with z() as z3:
reveal_type(z3)
z3.a = 1
z3.b = 2 (venv) clemence@CFA-CLEMENCE story-indexer % mypy ~/foo.py
~/foo.py:11: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
~/foo.py:24: note: Revealed type is "foo.Foo"
~/foo.py:26: error: "Foo" has no attribute "b" [attr-defined]
~/foo.py:29: note: Revealed type is "foo.Foo"
~/foo.py:31: error: "Foo" has no attribute "b" [attr-defined]
~/foo.py:37: note: Revealed type is "foo.Foo"
~/foo.py:39: error: "Foo" has no attribute "b" [attr-defined]
~/foo.py:44: note: Revealed type is "foo.Bar"
~/foo.py:46: error: "Bar" has no attribute "b" [attr-defined]
~/foo.py:49: note: Revealed type is "foo.Bar"
~/foo.py:51: error: "Bar" has no attribute "b" [attr-defined]
~/foo.py:57: note: Revealed type is "foo.Bar"
~/foo.py:59: error: "Bar" has no attribute "b" [attr-defined]
~/foo.py:64: note: Revealed type is "foo.Baz"
~/foo.py:69: note: Revealed type is "foo.Baz"
~/foo.py:77: note: Revealed type is "foo.Baz"
Found 7 errors in 1 file (checked 1 source file) |
C00L!
|
Tried this:
(venv) ***@***.***:~/MC/story-indexer$ git diff
diff --git a/indexer/story.py b/indexer/story.py
index 6830e45..af4fce3 100644
--- a/indexer/story.py
+++ b/indexer/story.py
@@ -4,7 +4,7 @@ import pickle
import re
from dataclasses import dataclass, field, fields
from pathlib import Path
-from typing import Any, Callable, Dict, List, Optional
+from typing import Any, Callable, Dict, List, Optional, TypeVar
from uuid import NAMESPACE_URL, uuid3
import cchardet as chardet
@@ -34,6 +34,9 @@ def class_to_member_name(original_class: Callable, private: bool = True) -> str:
return prefix + re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
+TStoryData = TypeVar("TStoryData", bound="StoryData")
+
+
@DataClass(kw_only=True)
class StoryData:
"""
@@ -64,7 +67,7 @@ class StoryData:
self.frozen = True
# Implementing typing on return:self is really finicky, just doing Any for now
- def __enter__(self) -> Any:
+ def __enter__(self: TStoryData) -> TStoryData:
self.frozen = False
return self
diff --git a/indexer/workers/fetcher/rss-queuer.py b/indexer/workers/fetcher/rss-queuer.py
index 121335c..528c83a 100644
--- a/indexer/workers/fetcher/rss-queuer.py
+++ b/indexer/workers/fetcher/rss-queuer.py
@@ -134,7 +134,8 @@ class RSSQueuer(Queuer):
# mypy reval_type(rss) in "with s.rss_entry() as rss" gives Any!!
rss = s.rss_entry()
with rss:
- rss.link = self.link
+ reveal_type(rss)
+ rss.limk = self.link
rss.domain = self.domain
rss.pub_date = self.pub_date
rss.title = self.title
Then ran mypy:
(venv) ***@***.***:~/MC/story-indexer$ mypy -m indexer.workers.fetcher.rss-queuer
....
indexer/workers/fetcher/rss-queuer.py:137: note: Revealed type is "indexer.story.RSSEntry"
So on the plus side, mypy sees the right type, but, as (unfortunately)
with all objects, mypy still won't scream about typos in attributes
(I introduced a typo: link -> limk). And adding __slots__ = ('link', ....)
to the RSSEntry declaration didn't change that.
…__setattr__ already validates member names at runtime...
|
I think (not 100% sure) this is because of the custom |
I think (not 100% sure) this is because of the custom [`__setattr__`](https://github.com/CodeForAfrica/story-indexer/blob/a7a8a8ec465f326b836af8836edb57c5240ca580/indexer/story.py#L71-L84) ... the question of which fields are/aren't allowed now becomes a runtime issue that mypy can't help us with.
Yes, that makes good sense.
|
The text was updated successfully, but these errors were encountered: