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

basic typing fixes #1456

Merged
merged 6 commits into from Jan 6, 2023
Merged

basic typing fixes #1456

merged 6 commits into from Jan 6, 2023

Conversation

nitzan-shaked
Copy link
Contributor

Hello there. I've run in a small number of typing issues in Textual during my everyday use, and thought I'd attempt a fix in Textual itself.

My ambitious goal is to run mypy --strict on all the codebase with zero warnings, and I'm about 50% of the way though. However that would be a non-small PR, so I thought I'd start with some obvious issues that should be easy to accept hopefully.

I've included here a fairly small number of changes, all of which I consider "obvious fixes" in the sense that they either expose actual bugs that exist now, or they break Python contracts, which are bugs waiting to happen.

I am going to annotate this PR with my reasoning below in a few minutes.

I hope this is useful. Cheers.

@@ -13,7 +14,7 @@ class DuplicateIds(Exception):


@rich.repr.auto(angular=True)
class NodeList(Sequence):
class NodeList(Sequence[Widget]):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sequence is Generic and needs a type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: importing Widget here causes cyclic dependency. Basically it makes DOM depend on Widget, and Widget itself obviously depends on DOM.

I resolved this by using T instead of Widget here. Apparently there's nothing in the code that needs an actual widget, only hashable things.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try putting Widget in quotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that, but isn't what I did now better? It really doesn't need to be Widget at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object is intended to be a container of Widgets. Better to type it as such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well in that case there's an actual cyclic dependency, no? I may be able to solve with quotes etc, but regardless there's a sort of "smell" to it. Why would DOMNode know about widgets? Anyway I'll have a look-see.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Works. (at least: passes mypy --strict and the tests...)

src/textual/_node_list.py Show resolved Hide resolved
src/textual/_node_list.py Show resolved Hide resolved
src/textual/css/errors.py Show resolved Hide resolved
src/textual/css/errors.py Show resolved Hide resolved
src/textual/renderables/sparkline.py Show resolved Hide resolved

bucket_index = 0
bars_rendered = 0
step = len(buckets) / width
step = len(buckets) // width
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE -- I believe this is the intention, but not sure. Either way this failed strict typing, which is how I noticed.

@@ -120,5 +121,5 @@ def frange(start, end, step):

with Live(opacity_panel, refresh_per_second=60) as live:
for value in itertools.cycle(frange(0, 1, 0.05)):
opacity_panel.value = value
opacity_panel.opacity = value
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an actual bug! opacity_panel does not have a value property, only opacity.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dead code written to test the module. We should delete it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will delete.
(EDIT: deleted; will appear as such when I push next)

src/textual/strip.py Show resolved Hide resolved
src/textual/widgets/_tree.py Show resolved Hide resolved
@@ -218,6 +219,8 @@ def contains_point(self, point: tuple[int, int]) -> bool:
def __contains__(self, other: Any) -> bool:
try:
x, y = other
assert isinstance(x, int)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't consider the lack of a check to be a bug per se. Not averse to a sanity check, but it is an expensive one. Not sure it is worth the effort.

I'd also avoid raising an assert, just to catch it on the next line.

@@ -63,6 +63,7 @@ def process_segments(
_from_color = Style.from_color
if opacity == 0:
for text, style, control in segments:
assert style is not None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically correct, but slow. There may be a lot of segments. Suggest ignoring or casting segments to say that style will not be None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do the latter.
(EDIT: done; will appear as such when I push next)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the assert now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops :) Removed.

@@ -120,5 +121,5 @@ def frange(start, end, step):

with Live(opacity_panel, refresh_per_second=60) as live:
for value in itertools.cycle(frange(0, 1, 0.05)):
opacity_panel.value = value
opacity_panel.opacity = value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dead code written to test the module. We should delete it.

@nitzan-shaked
Copy link
Contributor Author

@willmcgugan thank you for the review! I fixed everything save the math.sqrt thing, for which I left a comment and waiting for your decision.

invisible_style = _from_color(bgcolor=style.bgcolor)
yield _Segment(cell_len(text) * " ", invisible_style)
else:
for segment in segments:
text, style, control = segment
if not style:
text, maybe_style, control = segment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is a maybe style. It is a Style at this point. Styles do have __bool__ to indicate a null style, but it is still a Style object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still a maybe_style because in this branch I haven't cast segments to indicate it can't be None. I think you're saying I should do that in both branches. Will do.

@nitzan-shaked
Copy link
Contributor Author

@willmcgugan all comments addressed now, I believe.

@nitzan-shaked
Copy link
Contributor Author

Well that's annoying. The tests pass perfectly other than one test in one configuration because... CI was too slow (test_auto_refresh). I propose to ignore that, or re-run.

Also, I propose changing the measure. I might create a PR, I wonder if that works well in Windows etc.

Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@willmcgugan willmcgugan merged commit 70bded0 into Textualize:main Jan 6, 2023
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

Successfully merging this pull request may close these issues.

None yet

2 participants