Skip to content

Commit 1a3f5e3

Browse files
authored
Raise Minimum Python Version to 3.8 (#1597)
* raise minimum python version to 3.8 * try to fix entry points * use walrus and hasattr * update supported versions
1 parent b61482a commit 1a3f5e3

File tree

15 files changed

+133
-138
lines changed

15 files changed

+133
-138
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ jobs:
1818
os: [ubuntu-latest, macos-latest, windows-latest]
1919
experimental: [false]
2020
python-version: [
21-
"3.7",
2221
"3.8",
2322
"3.9",
2423
"3.10",
2524
"3.11",
26-
"pypy-3.7",
2725
"pypy-3.8",
2826
"pypy-3.9",
2927
]
@@ -85,9 +83,6 @@ jobs:
8583
run: |
8684
python -m pip install --upgrade pip
8785
pip install -e .[lint]
88-
- name: mypy 3.7
89-
run: |
90-
mypy --python-version 3.7 .
9186
- name: mypy 3.8
9287
run: |
9388
mypy --python-version 3.8 .

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ Library Version Python
6262
------------------------------ -----------
6363
2.x 2.6+, 3.4+
6464
3.x 2.7+, 3.5+
65-
4.x 3.7+
65+
4.0+ 3.7+
66+
4.3+ 3.8+
6667
============================== ===========
6768

6869

can/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"exceptions",
6262
"interface",
6363
"interfaces",
64+
"io",
6465
"listener",
6566
"logconvert",
6667
"log",

can/_entry_points.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import importlib
2+
import sys
3+
from dataclasses import dataclass
4+
from importlib.metadata import entry_points
5+
from typing import Any, List
6+
7+
8+
@dataclass
9+
class _EntryPoint:
10+
key: str
11+
module_name: str
12+
class_name: str
13+
14+
def load(self) -> Any:
15+
module = importlib.import_module(self.module_name)
16+
return getattr(module, self.class_name)
17+
18+
19+
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points,
20+
# "Compatibility Note".
21+
if sys.version_info >= (3, 10):
22+
23+
def read_entry_points(group: str) -> List[_EntryPoint]:
24+
return [
25+
_EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group)
26+
]
27+
28+
else:
29+
30+
def read_entry_points(group: str) -> List[_EntryPoint]:
31+
return [
32+
_EntryPoint(ep.name, *ep.value.split(":", maxsplit=1))
33+
for ep in entry_points().get(group, [])
34+
]

can/broadcastmanager.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
import sys
1111
import threading
1212
import time
13-
from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union
14-
15-
from typing_extensions import Final
13+
from typing import TYPE_CHECKING, Callable, Final, Optional, Sequence, Tuple, Union
1614

1715
from can import typechecking
1816
from can.message import Message

can/bus.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,21 @@
88
from abc import ABC, ABCMeta, abstractmethod
99
from enum import Enum, auto
1010
from time import time
11-
from typing import Any, Callable, Iterator, List, Optional, Sequence, Tuple, Union, cast
11+
from types import TracebackType
12+
from typing import (
13+
Any,
14+
Callable,
15+
Iterator,
16+
List,
17+
Optional,
18+
Sequence,
19+
Tuple,
20+
Type,
21+
Union,
22+
cast,
23+
)
24+
25+
from typing_extensions import Self
1226

1327
import can
1428
import can.typechecking
@@ -450,10 +464,15 @@ def shutdown(self) -> None:
450464
self._is_shutdown = True
451465
self.stop_all_periodic_tasks()
452466

453-
def __enter__(self):
467+
def __enter__(self) -> Self:
454468
return self
455469

456-
def __exit__(self, exc_type, exc_val, exc_tb):
470+
def __exit__(
471+
self,
472+
exc_type: Optional[Type[BaseException]],
473+
exc_value: Optional[BaseException],
474+
traceback: Optional[TracebackType],
475+
) -> None:
457476
self.shutdown()
458477

459478
def __del__(self) -> None:

can/interfaces/__init__.py

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
Interfaces contain low level implementations that interact with CAN hardware.
33
"""
44

5-
import sys
6-
from typing import Dict, Tuple, cast
5+
from typing import Dict, Tuple
6+
7+
from can._entry_points import read_entry_points
78

89
__all__ = [
910
"BACKENDS",
@@ -60,36 +61,12 @@
6061
"socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
6162
}
6263

63-
if sys.version_info >= (3, 8):
64-
from importlib.metadata import entry_points
65-
66-
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points,
67-
# "Compatibility Note".
68-
if sys.version_info >= (3, 10):
69-
BACKENDS.update(
70-
{
71-
interface.name: (interface.module, interface.attr)
72-
for interface in entry_points(group="can.interface")
73-
}
74-
)
75-
else:
76-
# The entry_points().get(...) causes a deprecation warning on Python >= 3.10.
77-
BACKENDS.update(
78-
{
79-
interface.name: cast(
80-
Tuple[str, str], tuple(interface.value.split(":", maxsplit=1))
81-
)
82-
for interface in entry_points().get("can.interface", [])
83-
}
84-
)
85-
else:
86-
from pkg_resources import iter_entry_points
8764

88-
BACKENDS.update(
89-
{
90-
interface.name: (interface.module_name, interface.attrs[0])
91-
for interface in iter_entry_points("can.interface")
92-
}
93-
)
65+
BACKENDS.update(
66+
{
67+
interface.key: (interface.module_name, interface.class_name)
68+
for interface in read_entry_points(group="can.interface")
69+
}
70+
)
9471

95-
VALID_INTERFACES = frozenset(BACKENDS.keys())
72+
VALID_INTERFACES = frozenset(sorted(BACKENDS.keys()))

can/io/generic.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
BinaryIO,
99
ContextManager,
1010
Iterable,
11+
Literal,
1112
Optional,
1213
TextIO,
1314
Type,
1415
Union,
1516
cast,
1617
)
1718

18-
from typing_extensions import Literal
19+
from typing_extensions import Self
1920

2021
from .. import typechecking
2122
from ..listener import Listener
@@ -65,7 +66,7 @@ def __init__(
6566
# for multiple inheritance
6667
super().__init__()
6768

68-
def __enter__(self) -> "BaseIOHandler":
69+
def __enter__(self) -> Self:
6970
return self
7071

7172
def __exit__(

can/io/logger.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
from abc import ABC, abstractmethod
99
from datetime import datetime
1010
from types import TracebackType
11-
from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, cast
11+
from typing import Any, Callable, Dict, Literal, Optional, Set, Tuple, Type, cast
1212

13-
from pkg_resources import iter_entry_points
14-
from typing_extensions import Literal
13+
from typing_extensions import Self
1514

15+
from .._entry_points import read_entry_points
1616
from ..listener import Listener
1717
from ..message import Message
1818
from ..typechecking import AcceptedIOType, FileLike, StringPathLike
@@ -89,8 +89,8 @@ def __new__( # type: ignore
8989
if not Logger.fetched_plugins:
9090
Logger.message_writers.update(
9191
{
92-
writer.name: writer.load()
93-
for writer in iter_entry_points("can.io.message_writer")
92+
writer.key: cast(Type[MessageWriter], writer.load())
93+
for writer in read_entry_points("can.io.message_writer")
9494
}
9595
)
9696
Logger.fetched_plugins = True
@@ -99,19 +99,19 @@ def __new__( # type: ignore
9999

100100
file_or_filename: AcceptedIOType = filename
101101
if suffix == ".gz":
102-
LoggerType, file_or_filename = Logger.compress(filename, **kwargs)
102+
logger_type, file_or_filename = Logger.compress(filename, **kwargs)
103103
else:
104-
LoggerType = cls._get_logger_for_suffix(suffix)
104+
logger_type = cls._get_logger_for_suffix(suffix)
105105

106-
return LoggerType(file=file_or_filename, **kwargs)
106+
return logger_type(file=file_or_filename, **kwargs)
107107

108108
@classmethod
109109
def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]:
110110
try:
111-
LoggerType = Logger.message_writers[suffix]
112-
if LoggerType is None:
111+
logger_type = Logger.message_writers[suffix]
112+
if logger_type is None:
113113
raise ValueError(f'failed to import logger for extension "{suffix}"')
114-
return LoggerType
114+
return logger_type
115115
except KeyError:
116116
raise ValueError(
117117
f'No write support for this unknown log format "{suffix}"'
@@ -130,15 +130,15 @@ def compress(
130130
raise ValueError(
131131
f"The file type {real_suffix} is currently incompatible with gzip."
132132
)
133-
LoggerType = cls._get_logger_for_suffix(real_suffix)
133+
logger_type = cls._get_logger_for_suffix(real_suffix)
134134
append = kwargs.get("append", False)
135135

136-
if issubclass(LoggerType, BinaryIOMessageWriter):
136+
if issubclass(logger_type, BinaryIOMessageWriter):
137137
mode = "ab" if append else "wb"
138138
else:
139139
mode = "at" if append else "wt"
140140

141-
return LoggerType, gzip.open(filename, mode)
141+
return logger_type, gzip.open(filename, mode)
142142

143143
def on_message_received(self, msg: Message) -> None:
144144
pass
@@ -275,7 +275,7 @@ def stop(self) -> None:
275275
"""
276276
self.writer.stop()
277277

278-
def __enter__(self) -> "BaseRotatingLogger":
278+
def __enter__(self) -> Self:
279279
return self
280280

281281
def __exit__(

0 commit comments

Comments
 (0)