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

chore: move types from shared-data to performance-metrics #15355

Merged
merged 8 commits into from
Jun 7, 2024

Conversation

DerekMaggio
Copy link
Contributor

@DerekMaggio DerekMaggio commented Jun 6, 2024

Overview

  • Moves performance-metrics types out of shared-data and into performance-metrics. Closes EXEC-508
  • Redeclare TypeVars and TypeAliases instead of centralizing them. Closes EXEC-509

Changelog

  • Change RobotContextState from enum to literal
  • Remove StubbedTracker's dep on SupportsTracking

Risk assessment

Low

@DerekMaggio DerekMaggio requested a review from a team as a code owner June 6, 2024 16:39
@DerekMaggio DerekMaggio marked this pull request as draft June 6, 2024 16:39
@DerekMaggio
Copy link
Contributor Author

DerekMaggio commented Jun 6, 2024

I am stuck. mypy is evaluating robot_context_tracker.py and failing with

src/performance_metrics/robot_context_tracker.py:121: error: Returning Any from function declared to return "_UnderlyingFunctionReturn"  [no-any-return]
src/performance_metrics/robot_context_tracker.py:124: error: Incompatible return value type (got "Callable[_UnderlyingFunctionParameters, Coroutine[Any, Any, _UnderlyingFunctionReturn]]", expected "Callable[_UnderlyingFunctionParameters, _UnderlyingFunctionReturn]")  [return-value]

For the first error, I can't figure out why wrapper's return is evaluated correctly but async_wrapper's is not.

For the second, I get that async_wrapper is returning a coroutine where the return type of the coroutine is _UnderlyingFunctionParameters. But I don't understand why it is evaluating the yield and send to Any. I am not yielding anything. I really don't understand what send is so no idea there.

@SyntaxColoring
Copy link
Contributor

SyntaxColoring commented Jun 6, 2024

For the first error, I can't figure out why wrapper's return is evaluated correctly but async_wrapper's is not.

It looks like if inspect.iscoroutinefunction(func_to_track): is, for some reason, incorrectly "narrowing" the type of func_to_track to CoroutineType[Any, Any, Any], losing the return type information. Looking into this.

For the second, I get that async_wrapper is returning a coroutine where the return type of the coroutine is _UnderlyingFunctionParameters. But I don't understand why it is evaluating the yield and send to Any. I am not yielding anything. I really don't understand what send is so no idea there.

I think the Anys there are a red herring. The actual problem is:

Suppose inner_decorator() is given a function async def underlying(i: int) -> float. That is a Callable[[int], Awaitable[float]]. In inner_decorator(), _UnderlyingFunctionReturn then represents Awaitable[float]. So when the async function async_wrapper() says it returns an _UnderlyingFunctionReturn, it's really saying it will return a doubly-wrapped Awaitable[Awaitable[float]], which it does not, actually.

I'll need to think some more about how to solve both of these. For now, feel free to slap # type: ignores on them. Sorry for leading us down a bad path!

@DerekMaggio
Copy link
Contributor Author

DerekMaggio commented Jun 6, 2024

No worries at all, not like I was on the right path either. Do you think it is better to type ignore or cast to the right type?

@DerekMaggio DerekMaggio marked this pull request as ready for review June 7, 2024 13:44
@SyntaxColoring
Copy link
Contributor

Do you think it is better to type ignore or cast to the right type?

No preference from me. Sometimes one or the other can be more specific, but I don't know about this case.

@DerekMaggio
Copy link
Contributor Author

DerekMaggio commented Jun 7, 2024

@SyntaxColoring, I need to have SupportsTracking and RobotContextState exist when the performance metrics package is not available (when installing the opentrons package from PyPI).
If I have those 2 classes in shared-data it’s not a problem cause shared data is a public package. If I put them in performance-metrics it becomes a problem because performance-metrics is not on PyPI.

Trying to figure out the best solution.

Stubbing a mypy Protocol and an enum seems weird. The Protocol contains no logic in the first place, and what would I stub for the enum?

See https://github.com/Opentrons/opentrons/actions/runs/9417949287/job/25944364043#step:9:1

@SyntaxColoring
Copy link
Contributor

It looks like you need SupportsTracking because you're doing this:

class StubbedTracker(SupportsTracking):
    ...

Which does require SupportsTracking at runtime, yeah.

I would solve that by just declaring it as

class StubbedTracker:
    ...

without explicitly subclassing SupportsTracking at runtime. The downside of this is that if you make a mistake and your StubbedTracker implementation doesn't correctly implement the protocol, the error will happen where you try to use the class, not where you declare it. I think that's fine, but there are workarounds if you think it's important. Something like:

if TYPE_CHECKING:
    # Enforce StubbedTracker implements SupportsTracking
    # without creating a runtime dependency on it.
    from performance_metrics import SupportsTracking
    _: Type["SupportsTracking"] = StubbedTracker

The RobotContextState enum is a bit harder because api really does need to create those values at runtime. I see two options:

  • Directly replace the enum with Literal["STARTING_UP", "CALIBRATING", ...], so you don't need to import anything at runtime to create the values.
  • Or, make the performance-metrics library accept arbitrary strs instead of this enum. Like how the Python logging module doesn't care what you name your loggers—logging.getLogger(...) accepts arbitrary strings.

@DerekMaggio DerekMaggio requested review from SyntaxColoring and a team and removed request for a team June 7, 2024 14:59
@DerekMaggio DerekMaggio requested a review from sfoster1 June 7, 2024 17:42
Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

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

Looks great, thank you!

@@ -142,6 +142,7 @@ def wrapper(

return result

# Whereas this is evaluated correctly as _UnderlyingFunctionReturn
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 a comment left over from debugging?

@DerekMaggio DerekMaggio merged commit b32c0cc into edge Jun 7, 2024
25 checks passed
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.

2 participants