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

Eliminate remaining implicit optional #1856

Closed
wants to merge 3 commits into from
Closed

Conversation

alexrudd2
Copy link
Collaborator

@alexrudd2 alexrudd2 commented Oct 26, 2023

In most cases, expanding the type to include | None is all that was required.

In others, I needed to add a simple check: AssertionError or similar.

I have split the changes into 3 commits, in case some are too much for one PR.

@alexrudd2 alexrudd2 requested a review from janiversen October 26, 2023 16:33
Copy link
Collaborator

@janiversen janiversen left a comment

Choose a reason for hiding this comment

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

Quite a number of questions/comments.

I think a lot of it comes because you change code and add typing at the same time.

pymodbus/client/serial.py Outdated Show resolved Hide resolved
@@ -156,17 +156,21 @@ def __init__(
self.is_server = is_server
self.is_closing = False

self.transport: asyncio.BaseTransport = None
self.loop: asyncio.AbstractEventLoop = None
self.loop: asyncio.AbstractEventLoop = None # type: ignore[assignment]
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 why I do not like the "| None", sometimes you use it, other times not...that its not really intuitive !

In my mind mypy is a bit stupid in this respect !

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This one was quite difficult because I could not figure out the intended usage of get_running_loop().

self.transport: asyncio.BaseTransport = None
self.loop: asyncio.AbstractEventLoop = None
self.loop: asyncio.AbstractEventLoop = None # type: ignore[assignment]
self.transport: asyncio.BaseTransport | None = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

And here you use it....any maintainer will think why on earth is self.loop without None and this one is with None...both are later used in the same way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The later callers are different. Calling code checks self.transport != None in all places. But the loop() code is more complicated.

self.reconnect_task: asyncio.Task = None
self.reconnect_delay_current: float = 0.0
self.reconnect_task: asyncio.Task | None = None
self.reconnect_delay_current: float | None = 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

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

this cannot be None as far as I remember the code... 0.0 is not None.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's unclear what the intended value of self.reconnect_delay_current is when self.reconnect_delay == None.

I could not easily decide between 0.0 and None in that case, so I went with None for fewer code changes.

Copy link
Collaborator

Choose a reason for hiding this comment

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

self.reconnect_delay_current is a value between self.reconnect_delay and self.reconnect_delay_max. so setting it to self.reconnect_delay is probably the most generic way.

pymodbus/transport/transport.py Show resolved Hide resolved
pymodbus/transport/transport.py Show resolved Hide resolved
pymodbus/transport/transport.py Show resolved Hide resolved
pymodbus/transport/transport_serial.py Outdated Show resolved Hide resolved
@@ -58,12 +58,12 @@ def close(self, exc=None):
def write(self, data) -> None:
"""Write some data to the transport."""
self._write_buffer.append(data)
if not self.poll_task:
if not self.poll_task and self.sync_serial:
Copy link
Collaborator

Choose a reason for hiding this comment

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

We cannot call write without sync_serial is created....this is the communication objects that comes from the connect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

"cannot" != "do not"

To satisfy mypy it must be totally impossible to call incorrectly.

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 a basic problem of mypy, it assumes all classes can be used randomly, which is NOT correct...and in reality one of my basic problems with mypy.

self.async_loop.add_writer(self.sync_serial.fileno(), self._write_ready)

def flush(self) -> None:
"""Clear output buffer and stops any more data being written"""
if not self.poll_task:
if not self.poll_task and self.sync_serial:
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here.

@alexrudd2
Copy link
Collaborator Author

I think a lot of it comes because you change code and add typing at the same time.

The final changes are of course the most difficult. :) It's not possible to prove to mypy all callers are correct without such code changes.

@janiversen
Copy link
Collaborator

I think a lot of it comes because you change code and add typing at the same time.

The final changes are of course the most difficult. :) It's not possible to prove to mypy all callers are correct without such code changes.

Well that is your challenge, because we will not start to add code, that ensures that we call the methods correctly...that would and endless amendment of code that needs to be maintained and basically only slows down the library.

@janiversen
Copy link
Collaborator

May I politely suggest you isolate the type changes in another PR which would be approved fast, and leave the rest in this PR:

@janiversen
Copy link
Collaborator

Please accept, that I do not want to be negative !!! you do a lot good for the library, for which I am grateful, but mypy have some severe limitations (or assumptions) which do not work well with real life code, and together we need to find a compromise that do not add extra maintenance just to please mypy.

@alexrudd2
Copy link
Collaborator Author

May I politely suggest you isolate the type changes in another PR which would be approved fast, and leave the rest in this PR:

Yep, I'll do that.

together we need to find a compromise that do not add extra maintenance just to please mypy.

Yes, of course. Partly I opened this PR so we can discuss and find those compromises. (The easy ones already got merged)

@alexrudd2 alexrudd2 marked this pull request as draft October 26, 2023 22:11
@alexrudd2
Copy link
Collaborator Author

Rebased + force-pushed to reduce the diff and show the remaining problems.

@janiversen
Copy link
Collaborator

In I think we need to have a relaxed view on mypy and not the 100% which it seems you support. Because we know how our code works, there are a lot of situations where mypy tells this is an error, but due to the way the code is build this only happens if someone misuse the code.

I am all in favour of using mypy where the code actually benefit from it, but e.g. adding code, which we have to maintain or deleting variables are not examples which I consider makes the code better.

@alexrudd2
Copy link
Collaborator Author

In I think we need to have a relaxed view on mypy and not the 100% which it seems you support. Because we know how our code works, there are a lot of situations where mypy tells this is an error, but due to the way the code is build this only happens if someone misuse the code.

The stricter the checks, the more it finds. Here's an example where mypy is currently too lenient and did not find this bug: #1804

Mypy is currently at about 50% (because untyped defs are not checked, and other stricter options are not enabled).

@janiversen
Copy link
Collaborator

I disagree, mypy make one false assumption especially about our internal code, it assumes that all methods can be called anytime....but the internal code have sequences which we know and do not violate......like e.g. self.sync_serial = None in init is just to get it declared when the object is created, no calls will see that later.

While I agree an API must be robust to invalid calls, our internals do not need to be just as robust, just like we in general do not validate parameters in internal calls.

@janiversen
Copy link
Collaborator

Your example is actually a good exammple to not make mypy stricter, the code works nice in our library but in the example it was used outside it the library.

@alexrudd2
Copy link
Collaborator Author

Your example is actually a good exammple to not make mypy stricter, the code works nice in our library but in the example it was used outside it the library.

I think you are missing the point. OK, the bug was first found by external calling code. However, there was also an incorrect caller hiding in pymodbus code. It was not caught by the tests because it's a rarely-used error handler, and not the "happy path".

        except serial.SerialException as exc:
       ---  self._protocol.loop.call_soon(self._call_connection_lost, exc)  # error
       +++  self.async_loop.call_soon(self._call_connection_lost, exc)  # fix
            self.close()

@janiversen
Copy link
Collaborator

No I did not miss the point, but I think you missed the point that this is imported code and currently work in progress to be converted fully to our needs.

@alexrudd2
Copy link
Collaborator Author

Closing in favor of #1888

@alexrudd2 alexrudd2 closed this Nov 12, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 23, 2023
@janiversen janiversen deleted the implicit-optional branch December 7, 2023 08:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants