Skip to content

Commit 25fe566

Browse files
authored
Improve can.Bus typing (#1557)
1 parent 7eac6f7 commit 25fe566

File tree

6 files changed

+114
-97
lines changed

6 files changed

+114
-97
lines changed

can/interface.py

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,20 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
5555
return cast(Type[BusABC], bus_class)
5656

5757

58-
class Bus(BusABC): # pylint: disable=abstract-method
59-
"""Bus wrapper with configuration loading.
58+
@util.deprecated_args_alias(
59+
deprecation_start="4.2.0",
60+
deprecation_end="5.0.0",
61+
bustype="interface",
62+
context="config_context",
63+
)
64+
def Bus(
65+
channel: Optional[Channel] = None,
66+
interface: Optional[str] = None,
67+
config_context: Optional[str] = None,
68+
ignore_config: bool = False,
69+
**kwargs: Any,
70+
) -> BusABC:
71+
"""Create a new bus instance with configuration loading.
6072
6173
Instantiates a CAN Bus of the given ``interface``, falls back to reading a
6274
configuration file from default locations.
@@ -99,45 +111,30 @@ class Bus(BusABC): # pylint: disable=abstract-method
99111
if the ``channel`` could not be determined
100112
"""
101113

102-
@staticmethod
103-
@util.deprecated_args_alias(
104-
deprecation_start="4.2.0",
105-
deprecation_end="5.0.0",
106-
bustype="interface",
107-
context="config_context",
108-
)
109-
def __new__( # type: ignore
110-
cls: Any,
111-
channel: Optional[Channel] = None,
112-
interface: Optional[str] = None,
113-
config_context: Optional[str] = None,
114-
ignore_config: bool = False,
115-
**kwargs: Any,
116-
) -> BusABC:
117-
# figure out the rest of the configuration; this might raise an error
118-
if interface is not None:
119-
kwargs["interface"] = interface
120-
if channel is not None:
121-
kwargs["channel"] = channel
122-
123-
if not ignore_config:
124-
kwargs = util.load_config(config=kwargs, context=config_context)
125-
126-
# resolve the bus class to use for that interface
127-
cls = _get_class_for_interface(kwargs["interface"])
128-
129-
# remove the "interface" key, so it doesn't get passed to the backend
130-
del kwargs["interface"]
131-
132-
# make sure the bus can handle this config format
133-
channel = kwargs.pop("channel", channel)
134-
if channel is None:
135-
# Use the default channel for the backend
136-
bus = cls(**kwargs)
137-
else:
138-
bus = cls(channel, **kwargs)
114+
# figure out the rest of the configuration; this might raise an error
115+
if interface is not None:
116+
kwargs["interface"] = interface
117+
if channel is not None:
118+
kwargs["channel"] = channel
119+
120+
if not ignore_config:
121+
kwargs = util.load_config(config=kwargs, context=config_context)
122+
123+
# resolve the bus class to use for that interface
124+
cls = _get_class_for_interface(kwargs["interface"])
125+
126+
# remove the "interface" key, so it doesn't get passed to the backend
127+
del kwargs["interface"]
128+
129+
# make sure the bus can handle this config format
130+
channel = kwargs.pop("channel", channel)
131+
if channel is None:
132+
# Use the default channel for the backend
133+
bus = cls(**kwargs)
134+
else:
135+
bus = cls(channel, **kwargs)
139136

140-
return cast(BusABC, bus)
137+
return bus
141138

142139

143140
def detect_available_configs(
@@ -146,7 +143,7 @@ def detect_available_configs(
146143
"""Detect all configurations/channels that the interfaces could
147144
currently connect with.
148145
149-
This might be quite time consuming.
146+
This might be quite time-consuming.
150147
151148
Automated configuration detection may not be implemented by
152149
every interface on every platform. This method will not raise

can/logger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _append_filter_argument(
8585
)
8686

8787

88-
def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus:
88+
def _create_bus(parsed_args: Any, **kwargs: Any) -> can.BusABC:
8989
logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"]
9090
can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)])
9191

@@ -99,7 +99,7 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus:
9999
if parsed_args.data_bitrate:
100100
config["data_bitrate"] = parsed_args.data_bitrate
101101

102-
return Bus(parsed_args.channel, **config) # type: ignore
102+
return Bus(parsed_args.channel, **config)
103103

104104

105105
def _parse_filters(parsed_args: Any) -> CanFilters:

can/util.py

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
cast,
2525
)
2626

27+
from typing_extensions import ParamSpec
28+
2729
import can
2830

2931
from . import typechecking
@@ -325,9 +327,15 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]:
325327
return None
326328

327329

328-
def deprecated_args_alias( # type: ignore
329-
deprecation_start: str, deprecation_end: Optional[str] = None, **aliases
330-
):
330+
P1 = ParamSpec("P1")
331+
T1 = TypeVar("T1")
332+
333+
334+
def deprecated_args_alias(
335+
deprecation_start: str,
336+
deprecation_end: Optional[str] = None,
337+
**aliases: Optional[str],
338+
) -> Callable[[Callable[P1, T1]], Callable[P1, T1]]:
331339
"""Allows to rename/deprecate a function kwarg(s) and optionally
332340
have the deprecated kwarg(s) set as alias(es)
333341
@@ -356,9 +364,9 @@ def library_function(new_arg):
356364
357365
"""
358366

359-
def deco(f):
367+
def deco(f: Callable[P1, T1]) -> Callable[P1, T1]:
360368
@functools.wraps(f)
361-
def wrapper(*args, **kwargs):
369+
def wrapper(*args: P1.args, **kwargs: P1.kwargs) -> T1:
362370
_rename_kwargs(
363371
func_name=f.__name__,
364372
start=deprecation_start,
@@ -373,10 +381,42 @@ def wrapper(*args, **kwargs):
373381
return deco
374382

375383

376-
T = TypeVar("T", BitTiming, BitTimingFd)
384+
def _rename_kwargs(
385+
func_name: str,
386+
start: str,
387+
end: Optional[str],
388+
kwargs: P1.kwargs,
389+
aliases: Dict[str, Optional[str]],
390+
) -> None:
391+
"""Helper function for `deprecated_args_alias`"""
392+
for alias, new in aliases.items():
393+
if alias in kwargs:
394+
deprecation_notice = (
395+
f"The '{alias}' argument is deprecated since python-can v{start}"
396+
)
397+
if end:
398+
deprecation_notice += (
399+
f", and scheduled for removal in python-can v{end}"
400+
)
401+
deprecation_notice += "."
402+
403+
value = kwargs.pop(alias)
404+
if new is not None:
405+
deprecation_notice += f" Use '{new}' instead."
406+
407+
if new in kwargs:
408+
raise TypeError(
409+
f"{func_name} received both '{alias}' (deprecated) and '{new}'."
410+
)
411+
kwargs[new] = value
412+
413+
warnings.warn(deprecation_notice, DeprecationWarning)
414+
377415

416+
T2 = TypeVar("T2", BitTiming, BitTimingFd)
378417

379-
def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T:
418+
419+
def check_or_adjust_timing_clock(timing: T2, valid_clocks: Iterable[int]) -> T2:
380420
"""Adjusts the given timing instance to have an *f_clock* value that is within the
381421
allowed values specified by *valid_clocks*. If the *f_clock* value of timing is
382422
already within *valid_clocks*, then *timing* is returned unchanged.
@@ -416,38 +456,6 @@ def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T:
416456
) from None
417457

418458

419-
def _rename_kwargs(
420-
func_name: str,
421-
start: str,
422-
end: Optional[str],
423-
kwargs: Dict[str, str],
424-
aliases: Dict[str, str],
425-
) -> None:
426-
"""Helper function for `deprecated_args_alias`"""
427-
for alias, new in aliases.items():
428-
if alias in kwargs:
429-
deprecation_notice = (
430-
f"The '{alias}' argument is deprecated since python-can v{start}"
431-
)
432-
if end:
433-
deprecation_notice += (
434-
f", and scheduled for removal in python-can v{end}"
435-
)
436-
deprecation_notice += "."
437-
438-
value = kwargs.pop(alias)
439-
if new is not None:
440-
deprecation_notice += f" Use '{new}' instead."
441-
442-
if new in kwargs:
443-
raise TypeError(
444-
f"{func_name} received both '{alias}' (deprecated) and '{new}'."
445-
)
446-
kwargs[new] = value
447-
448-
warnings.warn(deprecation_notice, DeprecationWarning)
449-
450-
451459
def time_perfcounter_correlation() -> Tuple[float, float]:
452460
"""Get the `perf_counter` value nearest to when time.time() is updated
453461

doc/bus.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
Bus
44
---
55

6-
The :class:`~can.Bus` provides a wrapper around a physical or virtual CAN Bus.
6+
The :class:`~can.BusABC` class provides a wrapper around a physical or virtual CAN Bus.
77

8-
An interface specific instance is created by instantiating the :class:`~can.Bus`
9-
class with a particular ``interface``, for example::
8+
An interface specific instance is created by calling the :func:`~can.Bus`
9+
function with a particular ``interface``, for example::
1010

1111
vector_bus = can.Bus(interface='vector', ...)
1212

@@ -77,13 +77,14 @@ See :meth:`~can.BusABC.set_filters` for the implementation.
7777
Bus API
7878
'''''''
7979

80-
.. autoclass:: can.Bus
80+
.. autofunction:: can.Bus
81+
82+
.. autoclass:: can.BusABC
8183
:class-doc-from: class
82-
:show-inheritance:
8384
:members:
8485
:inherited-members:
8586

86-
.. autoclass:: can.bus.BusState
87+
.. autoclass:: can.BusState
8788
:members:
8889
:undoc-members:
8990

doc/conf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@
126126
("py:class", "can.typechecking.CanFilter"),
127127
("py:class", "can.typechecking.CanFilterExtended"),
128128
("py:class", "can.typechecking.AutoDetectedConfig"),
129-
("py:class", "can.util.T"),
129+
("py:class", "can.util.T1"),
130+
("py:class", "can.util.T2"),
131+
("py:class", "~P1"),
130132
# intersphinx fails to reference some builtins
131133
("py:class", "asyncio.events.AbstractEventLoop"),
132134
("py:class", "_thread.allocate_lock"),

doc/internal-api.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,32 @@ They **might** implement the following:
5757
and thus might not provide message filtering:
5858

5959

60-
Concrete instances are usually created by :class:`can.Bus` which takes the users
60+
Concrete instances are usually created by :func:`can.Bus` which takes the users
6161
configuration into account.
6262

6363

6464
Bus Internals
6565
~~~~~~~~~~~~~
6666

67-
Several methods are not documented in the main :class:`can.Bus`
67+
Several methods are not documented in the main :class:`can.BusABC`
6868
as they are primarily useful for library developers as opposed to
69-
library users. This is the entire ABC bus class with all internal
70-
methods:
69+
library users.
7170

72-
.. autoclass:: can.BusABC
73-
:members:
74-
:private-members:
75-
:special-members:
71+
.. automethod:: can.BusABC.__init__
72+
73+
.. automethod:: can.BusABC.__iter__
74+
75+
.. automethod:: can.BusABC.__str__
76+
77+
.. autoattribute:: can.BusABC.__weakref__
78+
79+
.. automethod:: can.BusABC._recv_internal
80+
81+
.. automethod:: can.BusABC._apply_filters
82+
83+
.. automethod:: can.BusABC._send_periodic_internal
7684

85+
.. automethod:: can.BusABC._detect_available_configs
7786

7887

7988
About the IO module

0 commit comments

Comments
 (0)