diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb3f0bfd9..bd4713e87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,15 +21,16 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] env: [ - "py310", "py311", "py312", "py313", "py314", + "py315", # "py313t", # "py314t", - "pypy310", +# "py315t", "pypy311", + "pypy312", ] fail-fast: false steps: diff --git a/README.rst b/README.rst index 6e75d8d7d..bb72b7427 100644 --- a/README.rst +++ b/README.rst @@ -61,7 +61,7 @@ Library Version Python 4.0+ 3.7+ 4.3+ 3.8+ 4.6+ 3.9+ - main branch 3.10+ + main branch 3.11+ ============================== =========== diff --git a/can/bridge.py b/can/bridge.py index 2ebdff9f8..5449dbf3a 100644 --- a/can/bridge.py +++ b/can/bridge.py @@ -9,7 +9,7 @@ import errno import sys import time -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Final from can.cli import add_bus_arguments, create_bus_from_namespace @@ -52,14 +52,14 @@ def main() -> None: reader1_to_2 = RedirectReader(bus2) reader2_to_1 = RedirectReader(bus1) with Notifier(bus1, [reader1_to_2]), Notifier(bus2, [reader2_to_1]): - print(f"CAN Bridge (Started on {datetime.now(tz=timezone.utc)})") + print(f"CAN Bridge (Started on {datetime.now(tz=UTC)})") try: while True: time.sleep(1) except KeyboardInterrupt: pass - print(f"CAN Bridge (Stopped on {datetime.now(tz=timezone.utc)})") + print(f"CAN Bridge (Stopped on {datetime.now(tz=UTC)})") if __name__ == "__main__": diff --git a/can/bus.py b/can/bus.py index 7de26103e..72cd208ea 100644 --- a/can/bus.py +++ b/can/bus.py @@ -4,21 +4,13 @@ import contextlib import logging -import sys import threading from abc import ABC, abstractmethod from collections.abc import Callable, Iterator, Sequence from enum import Enum, auto from time import time from types import TracebackType -from typing import ( - cast, -) - -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self +from typing import Self, cast import can.typechecking from can.broadcastmanager import CyclicSendTaskABC, ThreadBasedCyclicSendTask diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index ea567f4bb..4a3123c59 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -64,6 +64,7 @@ class RxMessageStruct(ctypes.Structure): + _layout_ = "ms" _pack_ = 1 _fields_ = [ ("timestamp", ctypes.c_ulonglong), diff --git a/can/io/asc.py b/can/io/asc.py index 36e395612..be1e05b05 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -9,7 +9,7 @@ import logging import re from collections.abc import Generator -from datetime import datetime, timezone, tzinfo +from datetime import UTC, datetime, tzinfo from typing import Any, Final, Literal, TextIO from ..message import Message @@ -17,7 +17,7 @@ from ..util import channel2int, dlc2len, len2dlc from .generic import TextIOMessageReader, TextIOMessageWriter -_LOCAL_TZ: Final = datetime.now(timezone.utc).astimezone().tzinfo +_LOCAL_TZ: Final = datetime.now(UTC).astimezone().tzinfo CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF diff --git a/can/io/blf.py b/can/io/blf.py index 77bd02fae..b03bca41b 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -108,7 +108,7 @@ def timestamp_to_systemtime(timestamp: float | None) -> TSystemTime: if timestamp is None or timestamp < 631152000: # Probably not a Unix timestamp return 0, 0, 0, 0, 0, 0, 0, 0 - t = datetime.datetime.fromtimestamp(round(timestamp, 3), tz=datetime.timezone.utc) + t = datetime.datetime.fromtimestamp(round(timestamp, 3), tz=datetime.UTC) return ( t.year, t.month, @@ -131,7 +131,7 @@ def systemtime_to_timestamp(systemtime: TSystemTime) -> float: systemtime[5], systemtime[6], systemtime[7] * 1000, - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) return t.timestamp() except ValueError: diff --git a/can/io/generic.py b/can/io/generic.py index 3870bbe66..4dc349050 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -8,7 +8,6 @@ import locale import os -import sys from abc import ABC, abstractmethod from collections.abc import Iterable from contextlib import AbstractContextManager @@ -21,15 +20,11 @@ BinaryIO, Generic, Literal, + Self, TextIO, TypeVar, ) -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self - from ..listener import Listener from ..message import Message from ..typechecking import FileLike, StringPathLike diff --git a/can/io/logger.py b/can/io/logger.py index 04c93e04e..a6921d237 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -5,23 +5,18 @@ import gzip import os import pathlib -import sys from abc import ABC, abstractmethod from collections.abc import Callable -from datetime import datetime, timezone +from datetime import UTC, datetime from types import TracebackType from typing import ( Any, ClassVar, Final, Literal, + Self, ) -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self - from .._entry_points import read_entry_points from ..message import Message from ..typechecking import StringPathLike @@ -406,7 +401,7 @@ def _default_name(self) -> StringPathLike: new_name = ( path.stem.split(".")[0] + "_" - + datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H%M%S") + + datetime.now(tz=UTC).strftime("%Y-%m-%dT%H%M%S") + "_" + f"#{self.rollover_count:03}" + "".join(path.suffixes[-2:]) diff --git a/can/io/mf4.py b/can/io/mf4.py index e6dcd2f35..f728345ff 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -9,7 +9,7 @@ import heapq import logging from collections.abc import Generator, Iterator -from datetime import datetime, timezone +from datetime import UTC, datetime from hashlib import md5 from io import BufferedIOBase, BytesIO from pathlib import Path @@ -124,7 +124,7 @@ def __init__( ) super().__init__(file, mode="w+b") - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) self._mdf = cast("MDF4", MDF(version="4.10")) self._mdf.header.start_time = now self.last_timestamp = self._start_time = now.timestamp() diff --git a/can/io/trc.py b/can/io/trc.py index c9c315b6e..1f59b5787 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -10,7 +10,7 @@ import logging import os from collections.abc import Callable, Generator -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from enum import Enum from io import TextIOWrapper from typing import Any, TextIO @@ -67,7 +67,7 @@ def __init__( @property def start_time(self) -> datetime | None: if self._start_time: - return datetime.fromtimestamp(self._start_time, timezone.utc) + return datetime.fromtimestamp(self._start_time, UTC) return None def _extract_header(self) -> str: @@ -94,7 +94,7 @@ def _extract_header(self) -> str: logger.debug("TRCReader: Found start time '%s'", line) try: self._start_time = ( - datetime(1899, 12, 30, tzinfo=timezone.utc) + datetime(1899, 12, 30, tzinfo=UTC) + timedelta(days=float(line.split("=")[1])) ).timestamp() except IndexError: @@ -337,9 +337,7 @@ def _write_header_v1_0(self, start_time: datetime) -> None: self.file.writelines(line + "\n" for line in lines) def _write_header_v2_1(self, start_time: datetime) -> None: - header_time = start_time - datetime( - year=1899, month=12, day=30, tzinfo=timezone.utc - ) + header_time = start_time - datetime(year=1899, month=12, day=30, tzinfo=UTC) lines = [ ";$FILEVERSION=2.1", f";$STARTTIME={header_time/timedelta(days=1)}", @@ -397,7 +395,7 @@ def _format_message_init(self, msg: Message, channel: int) -> str: def write_header(self, timestamp: float) -> None: # write start of file header - start_time = datetime.fromtimestamp(timestamp, timezone.utc) + start_time = datetime.fromtimestamp(timestamp, UTC) if self.file_version == TRCFileVersion.V1_0: self._write_header_v1_0(start_time) diff --git a/can/listener.py b/can/listener.py index 285fca585..5616f110a 100644 --- a/can/listener.py +++ b/can/listener.py @@ -3,17 +3,11 @@ """ import asyncio -import sys import warnings from abc import ABC, abstractmethod from collections.abc import AsyncIterator from queue import Empty, SimpleQueue -from typing import Any - -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self +from typing import Any, Self from can.bus import BusABC from can.message import Message diff --git a/can/logger.py b/can/logger.py index e072eb6cb..198480d40 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,10 +1,8 @@ import argparse import errno import sys -from datetime import datetime, timezone -from typing import ( - TYPE_CHECKING, -) +from datetime import UTC, datetime +from typing import TYPE_CHECKING from can import BusState, Logger, SizedRotatingLogger from can.cli import ( @@ -107,7 +105,7 @@ def main() -> None: bus.state = BusState.PASSIVE print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") - print(f"Can Logger (Started on {datetime.now(tz=timezone.utc)})") + print(f"Can Logger (Started on {datetime.now(tz=UTC)})") logger: MessageWriter | BaseRotatingLogger if results.file_size: diff --git a/can/player.py b/can/player.py index 51231f3d1..b912c2c1e 100644 --- a/can/player.py +++ b/can/player.py @@ -9,7 +9,7 @@ import errno import math import sys -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import TYPE_CHECKING, cast from can import LogReader, MessageSync @@ -54,7 +54,7 @@ def _format_player_start_message(iteration: int, loop_count: float) -> str: else: loop_val = "∞" if math.isinf(loop_count) else str(loop_count) loop_info = f" [loop {iteration + 1}/{loop_val}]" - return f"Can LogReader (Started on {datetime.now(tz=timezone.utc)}){loop_info}" + return f"Can LogReader (Started on {datetime.now(tz=UTC)}){loop_info}" def main() -> None: diff --git a/can/util.py b/can/util.py index 765c9c9dc..f3b1040fd 100644 --- a/can/util.py +++ b/can/util.py @@ -17,12 +17,11 @@ from time import get_clock_info, perf_counter, time from typing import ( Any, + ParamSpec, TypeVar, cast, ) -from typing_extensions import ParamSpec - import can from . import typechecking diff --git a/doc/changelog.d/2128.removed.rst b/doc/changelog.d/2128.removed.rst new file mode 100644 index 000000000..c3cfc21ff --- /dev/null +++ b/doc/changelog.d/2128.removed.rst @@ -0,0 +1 @@ +Remove support for end-of-life Python 3.10. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3c2a93bc3..f95e6bf6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,9 @@ authors = [{ name = "python-can contributors" }] dependencies = [ "wrapt >= 1.10, < 3", "packaging >= 23.1", - "typing_extensions>=3.10.0.0", + "typing_extensions>=3.10.0.0;python_version<'3.12'", ] -requires-python = ">=3.10" +requires-python = ">=3.11" license = "LGPL-3.0-only" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -78,7 +78,7 @@ viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] mf4 = ["asammdf>=6.0.0"] -multicast = ["msgpack~=1.1.0"] +multicast = ["msgpack==1.*"] [dependency-groups] docs = [ @@ -103,8 +103,7 @@ test = [ "pytest-cov==7.1.*", "coverage==7.14.*", "hypothesis==6.*", - # hypothesis>=6.156 needs a Rust extension that does not support PyPy 3.10 - "hypothesis<6.156;platform_python_implementation=='PyPy' and python_version<'3.11'", + "hypothesis<6.156", "parameterized==0.9.*", ] dev = [ diff --git a/tox.ini b/tox.ini index 1d9d4cdc3..ccd02c862 100644 --- a/tox.ini +++ b/tox.ini @@ -26,11 +26,11 @@ commands = [testenv:py315] extras = canalystii - mf4 multicast gs-usb pywin32 serial + # mf4 depends on isal which is not available for py315 yet # still no windows-curses for py315 [testenv:{py313t,py314t,py315t,pypy310,pypy311,pypy312}] @@ -86,7 +86,6 @@ dependency_groups = lint extras = commands = - mypy --python-version 3.10 . mypy --python-version 3.11 . mypy --python-version 3.12 . mypy --python-version 3.13 .