-
-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add type annotations #6
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
To be complete, this PR needs
- a fix for CI failures (test coverage claims line 121 is not tested, the abstract method needs a
pragma: nocover
) - a CHANGES.rst note about the dropped Python 2.7 support
To avoid bitrot of type annotations I would also strongly like to have
- a
mypy
environment in tox.ini, similar to https://github.com/mgedmin/gitlab-trace/blob/master/tox.ini#L34-L39, except notypes-requests
of course) - a
mypy
job in GitHub actions workflow (one liner a la https://github.com/mgedmin/gitlab-trace/blob/master/.github/workflows/build.yml in the list of non-python-version tox envs)
The comments on the diff about my preferences are not blockers.
Please do a git pull before touching that file, because I just released 1.3.0 with a bugfix, so now there's a new header in there. |
de43671
to
dc82767
Compare
I added a GitHub action however Mypy does not really like the dynamic
I can do one of the following:
|
1ac7200
to
fb985a7
Compare
I came up with a cleaner descriptor-like API without the use of descriptors. |
de2ad55
to
d5bd6d3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR is getting big and perhaps ought to be split up?
I'm not sold on the Theme refactoring.
CI seems to be broken.
self.tree_style = getattr(self, styles.get("tree_style", "normal")) | ||
self.pid = getattr(self, styles.get("pid", "red")) | ||
self.process = getattr(self, styles.get("process", "green")) | ||
self.time_range = getattr(self, styles.get("time_range", "blue")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly I went overboard with the Theme magic. It was an experiment to see how to add configurable color highlighting without affecting too much of the code. I still like passing around Theme objects and invoking theme.thing()
before printing things. I regret abusing Theme.__new__
instead of writing a standalone def pick_theme(unicode: Optional[bool], color: Optional[bool]) -> Theme
.
I'm undecided about the magic __getattr__
. I'd like to study the problem of mypy dealing with __getattr__
in greater detail, which will require me to find a block of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first issue is that self.ctlseq
is not defined in Theme
so Theme.__getattr__()
cannot refer to it.
I can move Theme.__getattr__()
to AnsiTheme.__getattr__()
but then it will not be possible to call it on Theme
values.
I can resolve that by having Theme.__new__()
return Union[AnsiTheme, PlainTheme
instead of Theme
which will probably satisfy mypy
but the dynamic declaration of style properties still makes the code harder to reason about than necessary. Which is why I propose moving the style properties to class/object instantiation so that they are obvious up front.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would something like
StyleName = str
ColorName = str
class StyleProperty:
name: StyleName
def __get__(...): ...
def __set_name__(...): ...
class Theme:
pid = StyleProperty()
process = StyleProperty()
....
reset: str = ''
colors: Dict[ColorName, str] = {}
styles: Dict[StyleName, ColorName] = {}
class PlainTheme(Theme):
pass
class AnsiTheme(Theme):
reset = ''
colors = {
'red': ...
}
styles = {
'pid': 'red',
}
def pick_theme(...) -> Theme:
...
work? With some magic in StyleProperty.get that needs to return a callable (sort of like a custom bound method) that remembers which theme it came from, and the name of the style property, and can look up theme.colors.get(theme.styles.get(style_name), '')
and then wrap the argument between it and theme.reset
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see a benefit over the current proposal.
Other than pick_theme()
being a function rather than class method of ThemeFactory
. But then we would need to either define the helpers like should_use_color
inside the pick_method
function, or move them outside, losing the grouping and encapsulation provided by the class.
strace_process_tree.py
Outdated
if sys.version_info >= (3, 8): | ||
from typing import Literal | ||
else: | ||
from typing_extensions import Literal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, CI claims this line is not tested (on 3.8).
I usually do
try:
from typing import Literal # Python >= 3.8
except ImportError:
from typing_extensions import Literal
and mention except ImportError
in .coveragerc
's exclude_lines
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that at first but mypy does not like that: https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious: how does mypy complain about the try/except version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I'm not married to the try/except suggestion. I low-key like it because it requires only one pragma to satisfy coverage.py, as the import
statement inside the try block is always executed anyway. The sys.version check would # pragma: nocover
comments on both import
statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strace_process_tree.py:37: error: Incompatible import of "Literal" (imported name has type "typing_extensions._SpecialForm", local name has type "<typing special form>") [assignment]
return True | ||
|
||
|
||
def test_Theme_is_terminal_no_it_is_not(capsys): | ||
assert not stp.Theme.is_terminal() | ||
def test_Theme_is_terminal_no_it_is_not(capsys: CaptureFixture[str]) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, that's a lot of work. I've never tried to add type annotations to my tests before.
Anyway, mypy complains:
tests.py:5: error: Cannot find implementation or library stub for module named "pytest" [import]
tests.py:5: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
tests.py:80: error: Untyped decorator makes function "test_parse_timestamp" untyped [misc]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests.py:80: error: Untyped decorator makes function "test_parse_timestamp" untyped [misc]
I wonder if installing types-pytest
would help with this. Assuming types-pytest
exists. If not, we may have to abandon the idea of type-checking the test suite, because I'm not willing to give up @pytest.mark.parametrize()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, that's a lot of work. I've never tried to add type annotations to my tests before.
Most of that was done with search and replace so it was fine.
Anyway, mypy complains:
It passes for me locally. Maybe tox just needs to install pytest
, trying that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I forgot that tox -e mypy is a different environment that doesn't have pytest installed.
(I'm suddenly seeing the point of having a 'test' extra that depends on pytest, that I used to find a bit weird when I saw it used by other projects.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what ”having a 'test' extra that depends on pytest“ means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not important.
(It means having an extra_requires={'test': ['pytest']} in setup.py or whatever the equivalent is in pyproject.toml, and a
[testenv] extras = test` in tox.ini.)
aed4766
to
16584da
Compare
Not perfect: mypy will complain “Value of type "Style" is not indexable” because `Theme.__getattr__()` accesses `self.ctlseq`, which is only defined in the `AnsiTheme` subclass so it will consider it to be of `Style` type as returned by `__getattr__()`, rather than the actual `Dict[str,str]`.
Get rid of `Theme.__getattr__` method since that is hard to statically check. Use explicitly constructed static methods instead. Also split the factory out of `Theme` into `ThemeFactory`.
I have dropped the |
While trying to wrap my head around the codebase to debug #5, I started adding type hints. Posting it here in case you find it useful.