Skip to content

Commit 152a487

Browse files
authored
refactor(robot-server): fix/suppress type errors to enable strict mode (#8329)
1 parent 09efb81 commit 152a487

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+360
-270
lines changed

api/src/opentrons/hardware_control/api.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from collections import OrderedDict
77
from typing import (
88
Any,
9+
Callable,
910
Dict,
1011
Union,
1112
List,
@@ -45,6 +46,7 @@
4546
NoTipAttachedError,
4647
DoorState,
4748
DoorStateNotification,
49+
HardwareEvent,
4850
PipettePair,
4951
TipAttachedError,
5052
HardwareAction,
@@ -289,7 +291,9 @@ def _calculate_valid_attitude(self) -> DeckTransformState:
289291
self._robot_calibration.deck_calibration
290292
)
291293

292-
def register_callback(self, cb):
294+
def register_callback(
295+
self, cb: Callable[[Union[str, HardwareEvent]], None]
296+
) -> Callable[[], None]:
293297
"""Allows the caller to register a callback, and returns a closure
294298
that can be used to unregister the provided callback
295299
"""
@@ -1883,6 +1887,6 @@ def get_instrument_max_height(
18831887

18841888
return max_height
18851889

1886-
def clean_up(self):
1890+
def clean_up(self) -> None:
18871891
"""Get the API ready to stop cleanly."""
18881892
self._backend.clean_up()

api/src/opentrons/hardware_control/thread_manager.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
""" Manager for the :py:class:`.hardware_control.API` thread.
2-
"""
1+
"""Manager for the :py:class:`.hardware_control.API` thread."""
32
import threading
43
import logging
54
import asyncio
65
import functools
76
import weakref
8-
from typing import Generic, TypeVar, Any, Optional
7+
from typing import Any, Awaitable, Callable, Generic, Optional, TypeVar
98
from .adapters import SynchronousAdapter
109
from .modules.mod_abc import AbstractModule
10+
from .api import API as HardwareAPI
1111

1212
MODULE_LOG = logging.getLogger(__name__)
1313

@@ -95,15 +95,20 @@ class ThreadManager:
9595
>>> api_single_thread.sync.home() # call as blocking sync
9696
"""
9797

98-
def __init__(self, builder, *args, **kwargs):
98+
def __init__(
99+
self,
100+
builder: Callable[..., Awaitable[HardwareAPI]],
101+
*args: Any,
102+
**kwargs: Any,
103+
) -> None:
99104
"""Build the ThreadManager.
100105
101106
:param builder: The API function to use
102107
"""
103108

104-
self._loop = None
105-
self.managed_obj = None
106-
self.bridged_obj = None
109+
self._loop: Optional[asyncio.AbstractEventLoop] = None
110+
self.managed_obj: Optional[HardwareAPI] = None
111+
self.bridged_obj: Optional[CallBridger[Any]] = None
107112
self._sync_managed_obj: Optional[SynchronousAdapter] = None
108113
is_running = threading.Event()
109114
self._is_running = is_running

api/src/opentrons/hardware_control/util.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ def plan_arc(
2626
origin_point: Point,
2727
dest_point: Point,
2828
z_height: float,
29-
origin_cp: CriticalPoint = None,
30-
dest_cp: CriticalPoint = None,
31-
extra_waypoints: List[Tuple[float, float]] = None,
29+
origin_cp: Optional[CriticalPoint] = None,
30+
dest_cp: Optional[CriticalPoint] = None,
31+
extra_waypoints: Optional[List[Tuple[float, float]]] = None,
3232
) -> List[Tuple[Point, Optional[CriticalPoint]]]:
3333

3434
assert z_height >= max(origin_point.z, dest_point.z)

notify-server/notify_server/models/hardware_event/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55

66

77
HardwareEventPayload = Union[DoorStatePayload]
8+
9+
__all__ = ["HardwareEventPayload", "DoorStatePayload"]

robot-server/mypy.ini

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,48 @@
22
plugins = pydantic.mypy,decoy.mypy
33
show_error_codes = True
44
exclude = tests/(robot|service)
5-
6-
# TODO(mc, 2021-09-02): replace the the following flags
7-
# with `strict = True` when able
8-
warn_unused_configs = True
9-
disallow_subclassing_any = True
10-
check_untyped_defs = True
11-
warn_redundant_casts = True
12-
strict_equality = True
13-
warn_unused_ignores = True
14-
disallow_untyped_decorators = True
15-
16-
# TODO(mc, 2021-09-02): fix ~25 anys in generics
17-
# disallow_any_generics = True
18-
19-
# TODO(mc, 2021-09-02): fix ~150 untyped call errors
20-
# disallow_untyped_calls = True
21-
22-
# TODO(mc, 2021-09-02): fix ~200 untyped def errors
23-
# disallow_untyped_defs = True
24-
25-
# TODO(mc, 2021-09-02): fix ~80 incomplete def errors
26-
# disallow_incomplete_defs = True
27-
28-
# TODO(mc, 2021-09-02): fix ~35 implicit optionals
29-
# no_implicit_optional = True
30-
31-
# TODO(mc, 2021-09-02): fix ~30 any returns
32-
# warn_return_any = True
33-
34-
# TODO(mc, 2021-09-02): fix ~250 implicit re-export errors, some in FastAPI
35-
# FastAPI errors can be resolved by upgrading FastAPI
36-
# no_implicit_reexport = True
37-
38-
# end strict mode flags
5+
strict = True
396

407
[pydantic-mypy]
418
init_forbid_extra = True
429
init_typed = True
4310
warn_required_dynamic_aliases = True
4411
warn_untyped_fields = True
12+
13+
# TODO(mc, 2021-09-08): upgrade FastAPI and remove this override
14+
[mypy-fastapi.*]
15+
no_implicit_reexport = False
16+
17+
# TODO(mc, 2021-09-08): upgrade Pydantic and remove this override
18+
[mypy-pydantic.*]
19+
no_implicit_optional = False
20+
21+
# TODO(mc, 2021-09-08): fix and remove any / all of the
22+
# overrides below whenever able
23+
24+
[mypy-robot_server.service.*]
25+
# ~115 errors
26+
disallow_untyped_defs = False
27+
# ~60 errors
28+
disallow_untyped_calls = False
29+
# ~30 errors
30+
disallow_incomplete_defs = False
31+
# ~60 errors
32+
no_implicit_reexport = False
33+
34+
35+
[mypy-robot_server.robot.calibration.*]
36+
# ~115 errors
37+
disallow_untyped_defs = False
38+
# ~65 errors
39+
disallow_untyped_calls = False
40+
# ~40 errors
41+
disallow_incomplete_defs = False
42+
# ~15 errors
43+
warn_return_any = False
44+
45+
# ~25 errors, total
46+
[mypy-tests.conftest]
47+
disallow_untyped_defs = False
48+
disallow_untyped_calls = False
49+
disallow_incomplete_defs = False

robot-server/robot_server/app_state.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66
from fastapi import Request, WebSocket
77
from starlette.datastructures import State as AppState
8-
from typing import Generic, Optional, TypeVar
8+
from typing import cast, Generic, Optional, TypeVar
99

1010

1111
ValueT = TypeVar("ValueT")
@@ -25,7 +25,10 @@ def __init__(self, key: str) -> None:
2525

2626
def get_from(self, app_state: AppState) -> Optional[ValueT]:
2727
"""Get the value from state, returning None if not present."""
28-
return getattr(app_state, self._key, None)
28+
return cast(
29+
Optional[ValueT],
30+
getattr(app_state, self._key, None),
31+
)
2932

3033
def set_on(self, app_state: AppState, value: Optional[ValueT]) -> None:
3134
"""Set the value on state."""
@@ -36,8 +39,8 @@ async def get_app_state(
3639
# NOTE: both of these must be typed as non-optional to allow FastAPI's
3740
# dependency injection magic to work, but must have default values of
3841
# None in order to function at runtime, as only one will be present
39-
request: Request = None,
40-
websocket: WebSocket = None,
42+
request: Request = cast(Request, None),
43+
websocket: WebSocket = cast(WebSocket, None),
4144
) -> AppState:
4245
"""Get the global application's state from the framework.
4346
@@ -56,7 +59,7 @@ async def get_app_state(
5659
A dictionary-like object containing global application state.
5760
"""
5861
request_scope = request or websocket
59-
return request_scope.app.state # type: ignore[union-attr]
62+
return cast(AppState, request_scope.app.state)
6063

6164

6265
__all__ = ["AppState", "get_app_state"]

robot-server/robot_server/hardware.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import logging
44
from pathlib import Path
55
from fastapi import Depends, status
6-
from typing import Callable, cast
6+
from typing import Callable, Union, cast
77
from typing_extensions import Literal
88

9-
from opentrons import ThreadManager, initialize as initialize_api
9+
from opentrons import initialize as initialize_api
1010
from opentrons.config import feature_flags
1111
from opentrons.util.helpers import utc_now
12-
from opentrons.hardware_control import API as HardwareAPI
12+
from opentrons.hardware_control import API as HardwareAPI, ThreadManager
1313
from opentrons.hardware_control.simulator_setup import load_simulator
14-
from opentrons.hardware_control.types import HardwareEvent, HardwareEventType
14+
from opentrons.hardware_control.types import HardwareEvent, DoorStateNotification
1515

1616
from notify_server.clients import publisher
1717
from notify_server.settings import Settings as NotifyServerSettings
@@ -26,7 +26,7 @@
2626
log = logging.getLogger(__name__)
2727

2828
_hw_api = AppStateValue[HardwareAPI]("hardware_api")
29-
_init_task = AppStateValue[asyncio.Task]("hardware_init_task")
29+
_init_task = AppStateValue["asyncio.Task[None]"]("hardware_init_task")
3030
_event_unsubscribe = AppStateValue[Callable[[], None]]("hardware_event_unsubscribe")
3131

3232

@@ -142,8 +142,8 @@ def _initialize_event_watchers(app_state: AppState, hardware_api: HardwareAPI) -
142142
notify_server_settings.publisher_address.connection_string()
143143
)
144144

145-
def _publish_hardware_event(hw_event: HardwareEvent) -> None:
146-
if hw_event.event == HardwareEventType.DOOR_SWITCH_CHANGE:
145+
def _publish_hardware_event(hw_event: Union[str, HardwareEvent]) -> None:
146+
if isinstance(hw_event, DoorStateNotification):
147147
payload = DoorStatePayload(state=hw_event.new_state)
148148
else:
149149
return

robot-server/robot_server/protocols/dependencies.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,38 @@
66

77
from opentrons.protocol_runner import create_simulating_runner
88

9-
from robot_server.app_state import AppState, get_app_state
9+
from robot_server.app_state import AppState, AppStateValue, get_app_state
1010
from .protocol_store import ProtocolStore
1111
from .protocol_analyzer import ProtocolAnalyzer
1212
from .analysis_store import AnalysisStore
1313

1414
log = logging.getLogger(__name__)
1515

16-
_PROTOCOL_STORE_KEY = "protocol_store"
1716
_PROTOCOL_STORE_DIRECTORY = Path(gettempdir()) / "opentrons-protocols"
1817

19-
_ANALYSIS_STORE_KEY = "analysis_store"
18+
_protocol_store = AppStateValue[ProtocolStore]("protocol_store")
19+
_analysis_store = AppStateValue[AnalysisStore]("analysis_store")
2020

2121

2222
def get_protocol_store(app_state: AppState = Depends(get_app_state)) -> ProtocolStore:
2323
"""Get a singleton ProtocolStore to keep track of created protocols."""
24-
protocol_store = getattr(app_state, _PROTOCOL_STORE_KEY, None)
24+
protocol_store = _protocol_store.get_from(app_state)
2525

2626
if protocol_store is None:
2727
log.info(f"Storing protocols in {_PROTOCOL_STORE_DIRECTORY}")
2828
protocol_store = ProtocolStore(directory=_PROTOCOL_STORE_DIRECTORY)
29-
setattr(app_state, _PROTOCOL_STORE_KEY, protocol_store)
29+
_protocol_store.set_on(app_state, protocol_store)
3030

3131
return protocol_store
3232

3333

3434
def get_analysis_store(app_state: AppState = Depends(get_app_state)) -> AnalysisStore:
3535
"""Get a singleton AnalysisStore to keep track of created analyses."""
36-
analysis_store = getattr(app_state, _ANALYSIS_STORE_KEY, None)
36+
analysis_store = _analysis_store.get_from(app_state)
3737

3838
if analysis_store is None:
3939
analysis_store = AnalysisStore()
40-
setattr(app_state, _ANALYSIS_STORE_KEY, analysis_store)
40+
_analysis_store.set_on(app_state, analysis_store)
4141

4242
return analysis_store
4343

robot-server/robot_server/robot/calibration/check/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from functools import partial
44
from pydantic import BaseModel, Field
55

6+
from opentrons_shared_data.labware.dev_types import LabwareDefinition
67
from ..helper_classes import RequiredLabware, AttachedPipette
78

89
OffsetVector = Tuple[float, float, float]
@@ -97,7 +98,7 @@ class SessionCreateParams(BaseModel):
9798
description="Whether to use a calibration block in the"
9899
"calibration health check flow.",
99100
)
100-
tipRacks: List[dict] = Field(
101+
tipRacks: List[LabwareDefinition] = Field(
101102
[],
102103
description="A list of labware definitions to use in"
103104
"calibration health check",
@@ -113,7 +114,7 @@ class CalibrationCheckSessionStatus(BaseModel):
113114
comparisonsByPipette: ComparisonStatePerPipette
114115
labware: List[RequiredLabware]
115116
activeTipRack: RequiredLabware
116-
supportedCommands: List[Optional[str]] = Field(
117+
supportedCommands: List[str] = Field(
117118
..., description="A list of supported commands for this user flow"
118119
)
119120

0 commit comments

Comments
 (0)