Skip to content

Commit 4ee46a1

Browse files
Prevent subclassing of the Robot object (#21)
1 parent 21d4931 commit 4ee46a1

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

sr/robot3/robot.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pathlib import Path
99
from socket import socket
1010
from types import MappingProxyType
11-
from typing import Mapping, Optional
11+
from typing import Mapping, Optional, final
1212

1313
from . import game_specific, timeout
1414
from ._version import __version__
@@ -23,12 +23,15 @@
2323
from .raw_serial import RawSerial
2424
from .servo_board import ServoBoard
2525
from .simulator.time_server import TimeServer
26-
from .utils import IN_SIMULATOR, ensure_atexit_on_term, obtain_lock, singular
26+
from .utils import (
27+
IN_SIMULATOR, FinalMeta, ensure_atexit_on_term, obtain_lock, singular,
28+
)
2729

2830
logger = logging.getLogger(__name__)
2931

3032

31-
class Robot:
33+
@final
34+
class Robot(metaclass=FinalMeta):
3235
"""
3336
The main robot class that provides access to all the boards.
3437

sr/robot3/utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import socket
88
from abc import ABC, abstractmethod
99
from types import FrameType
10-
from typing import Any, Mapping, NamedTuple, Optional, TypeVar
10+
from typing import Any, Mapping, NamedTuple, Optional, Type, TypeVar
1111

1212
from serial.tools.list_ports import comports
1313
from serial.tools.list_ports_common import ListPortInfo
@@ -77,6 +77,18 @@ def identify(self) -> BoardIdentity:
7777
pass # pragma: no cover
7878

7979

80+
class FinalMeta(type):
81+
"""
82+
A meta class which ensures a given class cannot be subclassed.
83+
84+
It's recommended to additionally use typing.final.
85+
"""
86+
def __new__(cls, name: str, bases: tuple, classdict: dict) -> Type:
87+
if bases:
88+
raise TypeError(f"{name} cannot be sub-classed.")
89+
return super().__new__(cls, name, bases, dict(classdict))
90+
91+
8092
def map_to_int(
8193
x: float,
8294
in_min: float,

tests/test_sr_robot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,9 @@ def test_robot_discovery() -> None:
139139
assert robot.power_board == robot.boards[power_asset_tag]
140140
assert robot.servo_board == robot.boards[servo_asset_tag]
141141
assert robot.motor_board == robot.boards[motor_asset_tag]
142+
143+
144+
def test_cannot_subclass() -> None:
145+
with pytest.raises(TypeError, match="Robot cannot be sub-classed"):
146+
class MyRobot(Robot):
147+
pass

0 commit comments

Comments
 (0)