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: upgrade to 1.10.0 #156

Merged
merged 8 commits into from
Apr 25, 2024
Merged

mypy: upgrade to 1.10.0 #156

merged 8 commits into from
Apr 25, 2024

Conversation

achille-roussel
Copy link
Contributor

This PR upgrade mypy to 1.10.0, and enable --check-untyped-defs which does a much stricter pass. I then fixed all the type checks that were newly reported.

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
@achille-roussel achille-roussel added the enhancement New feature or request label Apr 25, 2024
@achille-roussel achille-roussel self-assigned this Apr 25, 2024
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
@@ -256,9 +256,9 @@ def primitive_func(input: Input) -> Output:
return OneShotScheduler(func).run(input)

primitive_func.__qualname__ = f"{name}_primitive"
primitive_func = durable(primitive_func)
durable_primitive_func = durable(primitive_func)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy complained here that the type of primitive_func didn't match after being wrapped by durable.

@@ -117,7 +117,7 @@ def do_POST(self):
)
max_age = timedelta(minutes=5)
try:
verify_request(signed_request, verification_key, max_age)
verify_request(signed_request, self.verification_key, max_age)
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.

@@ -83,7 +83,7 @@ def __str__(self):
_OUTPUT_TYPES: Dict[Type[Any], Callable[[Any], Status]] = {}


def status_for_error(error: Exception) -> Status:
def status_for_error(error: BaseException) -> Status:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turned out KeyboardInterrupt is not a subclass of Exception.

Comment on lines +90 to +91
self.roundtrips: OrderedDict[DispatchID, List[RoundTrip]] = OrderedDict()
self.collect_roundtrips = collect_roundtrips
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 simplifies call sites where we accessed the roundtrips field since it cannot be None anymore.

Comment on lines +149 to +150
assert isinstance(verification_key, Ed25519PublicKey)
assert verification_key.public_bytes_raw(), public_key2_bytes
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using assert instead of the methods helped mypy understand that verification_key couldn't be None.

@@ -65,7 +65,7 @@ def test_conversion_without_traceback(self):
original_exception = e

error = Error.from_exception(original_exception)
error.traceback = ""
error.traceback = b""
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.

@@ -72,7 +72,7 @@ def test_content_length_missing(self):
def test_content_length_too_large(self):
resp = self.client.post(
f"{self.endpoint}/dispatch.sdk.v1.FunctionService/Run",
data=b"a" * 16_000_001,
data={"msg": "a" * 16_000_001},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The post method apparently expected data to be an object.

Comment on lines 140 to 146
for i, (is_null, value) in enumerate(stack if stack is not None else []):
if is_null:
print(f"stack[{i}] = NULL")
else:
print(f"stack[{i}] = {value}")
print(f"BP = {bp}")
for i, block in enumerate(blocks):
for i, block in enumerate(blocks if blocks is not None else []):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These would have broken in the case where stack and blocks were None.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like mypy isn't smart enough to see that these are only None if frame_state >= FRAME_CLEARED, and that we're in a block where frame_state < FRAME_CLEARED.

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 analysis would be pretty difficult to make right? There are no constants in Python, FRAME_CLEARED could be altered concurrently by a thread, potentially in a module that imported this one.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think there are any points between checks where the GIL would be released and another thread allowed to run? There are calls to the C extension in there, but you have to explicitly release the GIL from C. mypy either assumes that all C extension calls may release the GIL, or it just doesn't propagate constraints across blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Assumptions about when the GIL can be released seem like asking for trouble, the rules are a bit unclear as to when this may happen; I would expect mypy not to try to be that smart because it's a lot of efforts for unclear rewards (false positives make the program safer anyways).

We could verify by pulling the value into a local instead of depending on the global FRAME_CLEARED variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh but even with a local, it doesn't know that frame_state = ext.get_frame_state(g) will return a value less than FRAME_CLEARED right?

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 just tried with a local and it still complains that the values might be None)

Copy link
Contributor

Choose a reason for hiding this comment

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

stack and blocks are set to None after frame_state is set, and if and only if frame_state < FRAME_CLEARED. The frame_state variable does not change after they're set to None.

I suspect the issue is that mypy forwards constraints into blocks, but doesn't carry them across blocks, because of potential path explosions. It seems to be smart enough to tighten types as you extend into if blocks, but in this case it doesn't carry the frame_state < FRAME_CLEARED constraint forward to an adjacent block.

No big deal, false positives are expected occasionally 🙂

@@ -30,7 +30,7 @@ fmt-check:
exit $$((isort_status + black_status))

typecheck:
$(PYTHON) -m mypy src tests
$(PYTHON) -m mypy --check-untyped-defs src tests examples
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're doing type checks on the examples directory as well now.

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
…om-test-package

test: drop FastAPI dependency
@achille-roussel achille-roussel merged commit a47ae12 into main Apr 25, 2024
10 checks passed
@achille-roussel achille-roussel deleted the mypy-1.10.0 branch April 25, 2024 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants