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

mypy compliance #533

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft

mypy compliance #533

wants to merge 18 commits into from

Conversation

liamhuber
Copy link
Member

@liamhuber liamhuber commented Jan 8, 2025

At the last meeting of December, @niklassiemer indicated he was fine learning whatever typing stuff was necessary in the event he takes over maintenance, so this PR is intended to close #514.

My current plan is to take any low-hanging fruit (e.g. hinting typing.Callable instead of callable) and just commit it directly here, but to break larger changes into stacked PRs so it's easier to view the diff and see intentions. I started using a file-based approach and have a branch for getting the channels sub-module compliant, but for things in the mixins it may make more sense to group the PRs so they touch multiple files, but updating a single root class.

@niklassiemer, you may want to keep an eye on this series of PRs just to get a rough sense of what was involved in this transition.

@XzzX, I'll ping you for review on this root branch when I feel it's done, but I'll freely merge the feature/file branches into it without delay. Nonetheless, feel free to also keep an eye on the whole series of PRs and interject whenever you have insight, and/or just stack fixes you want right on top of here.

EDIT: as long as it's in draft state, I'm not going to sweat about ruff and black failures. Definitely before merging, and I'll try to patch them up when I get a chance, but I am going to prioritize avoiding merge conflicts with any stacked branches over keeping the formatters permanently happy.

Progress tracker: mypy complaints

typing._UnionGenericAlias definitively _does_ exist.

Signed-off-by: liamhuber <[email protected]>
Based on @jan-janssen's jobs for other pyiron repos

Signed-off-by: liamhuber <[email protected]>
Copy link

github-actions bot commented Jan 8, 2025

Binder 👈 Launch a binder notebook on branch pyiron/pyiron_workflow/mypy

@liamhuber
Copy link
Member Author

liamhuber commented Jan 8, 2025

Windows tests failed, but CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://conda.anaconda.org/conda-forge/noarch/repodata.json>, so hopefully it is just a hiccup.

EDIT: Yep. Better now.

* Leverage generics for connection partners

Signed-off-by: liamhuber <[email protected]>

* Break apart connection error message

So we only reference type hints when they're there

Signed-off-by: liamhuber <[email protected]>

* Hint connections type more specifically

Signed-off-by: liamhuber <[email protected]>

* Hint disconnect more specifically

Signed-off-by: liamhuber <[email protected]>

* Use Self in disconnection hints

Signed-off-by: liamhuber <[email protected]>

* Use Self to hint value_receiver

Signed-off-by: liamhuber <[email protected]>

* Devolve responsibility for connection validity

Otherwise mypy has trouble telling that data channels really are operating on a connection partner, since the `super()` call could wind up pointing anywhere.

Signed-off-by: liamhuber <[email protected]>

* Fix typing in channel tests

Signed-off-by: liamhuber <[email protected]>

* 🐛 Return the message

Signed-off-by: liamhuber <[email protected]>

* Fix typing in figuring out who is I/O

Signed-off-by: liamhuber <[email protected]>

* Recast connection parters as class method

mypy complained about the class-level attribute access I was using to get around circular references. This is a bit more verbose, but otherwise a fine alternative.

Signed-off-by: liamhuber <[email protected]>

* Match Accumulating input signal call to parent

Signed-off-by: liamhuber <[email protected]>

---------

Signed-off-by: liamhuber <[email protected]>
Copy link

codacy-production bot commented Jan 8, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.02% (target: -1.00%) 97.18%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (53a5c14) 3356 3070 91.48%
Head commit (801bddb) 3401 (+45) 3112 (+42) 91.50% (+0.02%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#533) 142 138 97.18%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

Codacy stopped sending the deprecated coverage status on June 5th, 2024. Learn more

@coveralls
Copy link

coveralls commented Jan 8, 2025

Pull Request Test Coverage Report for Build 12774551989

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 103 unchanged lines in 13 files lost coverage.
  • Overall coverage increased (+0.02%) to 91.502%

Files with Coverage Reduction New Missed Lines %
nodes/function.py 1 98.41%
type_hinting.py 1 97.73%
nodes/transform.py 3 98.26%
find.py 3 90.32%
topology.py 5 88.61%
mixin/run.py 6 93.75%
nodes/macro.py 7 92.2%
mixin/preview.py 8 93.29%
nodes/composite.py 11 91.71%
io.py 14 94.4%
Totals Coverage Status
Change from base Build 12773583061: 0.02%
Covered Lines: 3112
Relevant Lines: 3401

💛 - Coveralls

@liamhuber
Copy link
Member Author

I currently only point mypy at the main module; I'd like to make the tests compliant as well, but that can be done at a later time in a separate PR.

This is just a little QoL thing; the current script runs the jobs twice every time I push, and it's annoying me.

Signed-off-by: liamhuber <[email protected]>
Signed-off-by: liamhuber <[email protected]>
Copy link
Contributor

@XzzX XzzX left a comment

Choose a reason for hiding this comment

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

I hope I got the idea of the channels, right.

pyiron_workflow/channels.py Outdated Show resolved Hide resolved
@@ -108,21 +115,18 @@ def full_label(self) -> str:
"""A label combining the channel's usual label and its owner's semantic path"""
return f"{self.owner.full_label}.{self.label}"

def _valid_connection(self, other: Channel) -> bool:
@abstractmethod
def _valid_connection(self, other: object) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def _valid_connection(self, other: object) -> bool:
def _valid_connection(self, other: ConnectionPartner) -> bool:

We can statically check if connection attempt is valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

isinstance can be done here and called in subclasses via super. We could get rid of connection_partner_type, if we want to or make it non-abstract.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I agree, you'd absolutely think so, right? But as far as I can tell, this is not allowed. The first problem is that ConnectionPartner is usable only at type-checking time and has no impact on runtime, so we can't do things like isinstance(other, ConnectionPartner) because, unfortunately, at runtime the concrete child classes never convert this to the actual class they use. So you wind up with an error that isinstance is expecting something type-y and not a TypeVar. Here is a demo snippet to play with:

import abc
import typing

SomeThing = typing.TypeVar("SomeThing")

class ThingChecker(typing.Generic[SomeThing], abc.ABC):
    def is_it_the_thing(self, thing: SomeThing) -> bool:
        return isinstance(thing, SomeThing)

    def what_is_the_thing(self) -> str:
        print(f"The thing is {SomeThing}")

class StringChecker(ThingChecker[str]):
    pass

sc = StringChecker()
sc.what_is_the_thing()
sc.is_it_the_thing(1), sc.is_it_the_thing("one")

The second problem is that mypy is a little persnickety about super() calls. I guess this is because python allows multiple inheritance, so it is not as sure as it would like to be about what the super call will do. I'm confident we could get around this by invoking something like ParentClass.method_we_want instead of super().method_we_want (or similar). I also can't currently rule out that we could get super() itself working with a bit more massaging. At any rate, my first crack at getting this to work nicely failed, so I took the most expedient route and avoided super() altogether. If I learn a nice pattern for this in the future, I'd be happy to come back and fix it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I agree, you'd absolutely think so, right? But as far as I can tell, this is not allowed. The first problem is that ConnectionPartner is usable only at type-checking time and has no impact on runtime, so we can't do things like isinstance(other, ConnectionPartner) because, unfortunately, at runtime the concrete child classes never convert this to the actual class they use. So you wind up with an error that isinstance is expecting something type-y and not a TypeVar. Here is a demo snippet to play with:

:( I got carried away with what is possible in other languages. You are right.

Copy link
Contributor

Choose a reason for hiding this comment

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

The second problem is that mypy is a little persnickety about super() calls. I guess this is because python allows multiple inheritance, so it is not as sure as it would like to be about what the super call will do. I'm confident we could get around this by invoking something like ParentClass.method_we_want instead of super().method_we_want (or similar). I also can't currently rule out that we could get super() itself working with a bit more massaging. At any rate, my first crack at getting this to work nicely failed, so I took the most expedient route and avoided super() altogether. If I learn a nice pattern for this in the future, I'd be happy to come back and fix it.

What error are you referring to? This error?

pyiron_workflow/channels.py:645: error: Signature of "__call__" incompatible with supertype "SignalChannel"  [override]
pyiron_workflow/channels.py:645: note:      Superclass:
pyiron_workflow/channels.py:645: note:          def __call__(self) -> None
pyiron_workflow/channels.py:645: note:      Subclass:
pyiron_workflow/channels.py:645: note:          def __call__(self, other: OutputSignal) -> None

This is a real problem.

Copy link
Member Author

@liamhuber liamhuber Jan 10, 2025

Choose a reason for hiding this comment

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

That's not the problem I'm referring to. Also, where did you generate that? It looks like the incompatibility between InlutSignal and AccumulatingSignal, but I fixed that.(Maybe I'm looking at the wrong branch here?)

Edit: ah, we're in the base PR. Yes, this is was easily fixed in the PR addressing channels.

@XzzX
Copy link
Contributor

XzzX commented Jan 9, 2025

@XzzX, I'll ping you for review on this root branch when I feel it's done, but I'll freely merge the feature/file branches into it without delay. Nonetheless, feel free to also keep an eye on the whole series of PRs and interject whenever you have insight, and/or just stack fixes you want right on top of here.

I favor smaller piece to review. I do not think I will be able to do a review of the final branch. But I can have a look at the smaller pieces if you want.

It was necessary for python<3.10, but we dropped support for that, so we can get rid of the ugly, non-public hint.

Signed-off-by: liamhuber <[email protected]>
@liamhuber
Copy link
Member Author

@XzzX, I'll ping you for review on this root branch when I feel it's done, but I'll freely merge the feature/file branches into it without delay. Nonetheless, feel free to also keep an eye on the whole series of PRs and interject whenever you have insight, and/or just stack fixes you want right on top of here.

I favor smaller piece to review. I do not think I will be able to do a review of the final branch. But I can have a look at the smaller pieces if you want.

Yes, that's fair. Ok, so then we'll modify slightly: I'll ping you for review on each of the smaller stacked PRs instead and you're free to review them (or not) at your leisure. I'll leave them open for as long as I can, but if I'm worried about merge conflicts before you have a chance to get to it, I'll still just merge them down. You can nonetheless provide feedback on them post-merge, and we can simply slap on patching branches when it's convenient, ala #535.

* Refactor: rename

Move from "partner" language to "conjugate" language

Signed-off-by: liamhuber <[email protected]>

* Explicitly decompose conjugate behaviour

Into flavor and IO components

Signed-off-by: liamhuber <[email protected]>

* Tidying

Signed-off-by: liamhuber <[email protected]>

* Narrow hint on connection copying

Signed-off-by: liamhuber <[email protected]>

---------

Signed-off-by: liamhuber <[email protected]>
* Refactor: rename

Move from "partner" language to "conjugate" language

Signed-off-by: liamhuber <[email protected]>

* Explicitly decompose conjugate behaviour

Into flavor and IO components

Signed-off-by: liamhuber <[email protected]>

* Tidying

Signed-off-by: liamhuber <[email protected]>

* Narrow hint on connection copying

Signed-off-by: liamhuber <[email protected]>

* Apply hints to IO panels

Signed-off-by: liamhuber <[email protected]>

* Narrow type

Signed-off-by: liamhuber <[email protected]>

* Don't reuse variable

Signed-off-by: liamhuber <[email protected]>

* Ruff: sort imports

Signed-off-by: liamhuber <[email protected]>

* 🐛 fix type hint

Signed-off-by: liamhuber <[email protected]>

* Add more hints

Signed-off-by: liamhuber <[email protected]>

---------

Signed-off-by: liamhuber <[email protected]>
The instance check to see if a connection candidate has the correct (conjugate) type now occurs only _once_ in the parent `Channel` class. `Channel._valid_connection` is the repurposed to check for validity inside the scope of the classes already lining up, and defaults to simply returning `True` in the base class. `DataChannel` overrides it to do the type hint comparison.

Changes inspired by [conversation](#533 (comment)) with @XzzX.

Signed-off-by: liamhuber <[email protected]>
liamhuber and others added 4 commits January 10, 2025 16:06
* Hint init properties

Signed-off-by: liamhuber <[email protected]>

* Hint local function

Signed-off-by: liamhuber <[email protected]>

* Add stricter return and hint

Signed-off-by: liamhuber <[email protected]>

* 🐛 Hint tuple[] not ()

Signed-off-by: liamhuber <[email protected]>

* Black

Signed-off-by: liamhuber <[email protected]>

---------

Signed-off-by: liamhuber <[email protected]>
* Don't overload typed variable

Signed-off-by: liamhuber <[email protected]>

* Add (and more specific) return hint(s)

To the one function missing one

Signed-off-by: liamhuber <[email protected]>

* Add module docstring

Signed-off-by: liamhuber <[email protected]>

* Catch module spec failures

Signed-off-by: liamhuber <[email protected]>

* Force mypy to accept the design feature

That we _want_ callers to be able to get abstract classes if they request them

Signed-off-by: liamhuber <[email protected]>

* Black

Signed-off-by: liamhuber <[email protected]>

* Ruff import sort

Signed-off-by: liamhuber <[email protected]>

---------

Signed-off-by: liamhuber <[email protected]>
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.

Include a static type checker?
3 participants