Skip to content

Commit b61482a

Browse files
authored
can.io: Distinguish Text/Binary-IO for Reader/Writer classes. (#1585)
1 parent 97f1e16 commit b61482a

File tree

9 files changed

+81
-33
lines changed

9 files changed

+81
-33
lines changed

can/io/asc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ..message import Message
1515
from ..typechecking import StringPathLike
1616
from ..util import channel2int, dlc2len, len2dlc
17-
from .generic import FileIOMessageWriter, MessageReader
17+
from .generic import TextIOMessageReader, TextIOMessageWriter
1818

1919
CAN_MSG_EXT = 0x80000000
2020
CAN_ID_MASK = 0x1FFFFFFF
@@ -24,7 +24,7 @@
2424
logger = logging.getLogger("can.io.asc")
2525

2626

27-
class ASCReader(MessageReader):
27+
class ASCReader(TextIOMessageReader):
2828
"""
2929
Iterator of CAN messages from a ASC logging file. Meta data (comments,
3030
bus statistics, J1939 Transport Protocol messages) is ignored.
@@ -308,7 +308,7 @@ def __iter__(self) -> Generator[Message, None, None]:
308308
self.stop()
309309

310310

311-
class ASCWriter(FileIOMessageWriter):
311+
class ASCWriter(TextIOMessageWriter):
312312
"""Logs CAN data to an ASCII log file (.asc).
313313
314314
The measurement starts with the timestamp of the first registered message.

can/io/blf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ..message import Message
2323
from ..typechecking import StringPathLike
2424
from ..util import channel2int, dlc2len, len2dlc
25-
from .generic import FileIOMessageWriter, MessageReader
25+
from .generic import BinaryIOMessageReader, FileIOMessageWriter
2626

2727
TSystemTime = Tuple[int, int, int, int, int, int, int, int]
2828

@@ -132,7 +132,7 @@ def systemtime_to_timestamp(systemtime: TSystemTime) -> float:
132132
return 0
133133

134134

135-
class BLFReader(MessageReader):
135+
class BLFReader(BinaryIOMessageReader):
136136
"""
137137
Iterator of CAN messages from a Binary Logging File.
138138

can/io/canutils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from can.message import Message
1111

1212
from ..typechecking import StringPathLike
13-
from .generic import FileIOMessageWriter, MessageReader
13+
from .generic import TextIOMessageReader, TextIOMessageWriter
1414

1515
log = logging.getLogger("can.io.canutils")
1616

@@ -23,7 +23,7 @@
2323
CANFD_ESI = 0x02
2424

2525

26-
class CanutilsLogReader(MessageReader):
26+
class CanutilsLogReader(TextIOMessageReader):
2727
"""
2828
Iterator over CAN messages from a .log Logging File (candump -L).
2929
@@ -122,7 +122,7 @@ def __iter__(self) -> Generator[Message, None, None]:
122122
self.stop()
123123

124124

125-
class CanutilsLogWriter(FileIOMessageWriter):
125+
class CanutilsLogWriter(TextIOMessageWriter):
126126
"""Logs CAN data to an ASCII log file (.log).
127127
This class is is compatible with "candump -L".
128128

can/io/csv.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from can.message import Message
1616

1717
from ..typechecking import StringPathLike
18-
from .generic import FileIOMessageWriter, MessageReader
18+
from .generic import TextIOMessageReader, TextIOMessageWriter
1919

2020

21-
class CSVReader(MessageReader):
21+
class CSVReader(TextIOMessageReader):
2222
"""Iterator over CAN messages from a .csv file that was
2323
generated by :class:`~can.CSVWriter` or that uses the same
2424
format as described there. Assumes that there is a header
@@ -67,7 +67,7 @@ def __iter__(self) -> Generator[Message, None, None]:
6767
self.stop()
6868

6969

70-
class CSVWriter(FileIOMessageWriter):
70+
class CSVWriter(TextIOMessageWriter):
7171
"""Writes a comma separated text file with a line for
7272
each message. Includes a header line.
7373

can/io/generic.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
"""Contains generic base classes for file IO."""
2+
import gzip
23
import locale
34
from abc import ABCMeta
45
from types import TracebackType
56
from typing import (
67
Any,
8+
BinaryIO,
79
ContextManager,
810
Iterable,
911
Optional,
12+
TextIO,
1013
Type,
14+
Union,
1115
cast,
1216
)
1317

@@ -105,5 +109,21 @@ def file_size(self) -> int:
105109
return self.file.tell()
106110

107111

112+
class TextIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta):
113+
file: TextIO
114+
115+
116+
class BinaryIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta):
117+
file: Union[BinaryIO, gzip.GzipFile]
118+
119+
108120
class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta):
109121
"""The base class for all readers."""
122+
123+
124+
class TextIOMessageReader(MessageReader, metaclass=ABCMeta):
125+
file: TextIO
126+
127+
128+
class BinaryIOMessageReader(MessageReader, metaclass=ABCMeta):
129+
file: Union[BinaryIO, gzip.GzipFile]

can/io/logger.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
from .blf import BLFWriter
2121
from .canutils import CanutilsLogWriter
2222
from .csv import CSVWriter
23-
from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter
23+
from .generic import (
24+
BaseIOHandler,
25+
BinaryIOMessageWriter,
26+
FileIOMessageWriter,
27+
MessageWriter,
28+
)
2429
from .mf4 import MF4Writer
2530
from .printer import Printer
2631
from .sqlite import SqliteWriter
@@ -94,20 +99,28 @@ def __new__( # type: ignore
9499

95100
file_or_filename: AcceptedIOType = filename
96101
if suffix == ".gz":
97-
suffix, file_or_filename = Logger.compress(filename, **kwargs)
102+
LoggerType, file_or_filename = Logger.compress(filename, **kwargs)
103+
else:
104+
LoggerType = cls._get_logger_for_suffix(suffix)
105+
106+
return LoggerType(file=file_or_filename, **kwargs)
98107

108+
@classmethod
109+
def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]:
99110
try:
100111
LoggerType = Logger.message_writers[suffix]
101112
if LoggerType is None:
102113
raise ValueError(f'failed to import logger for extension "{suffix}"')
103-
return LoggerType(file=file_or_filename, **kwargs)
114+
return LoggerType
104115
except KeyError:
105116
raise ValueError(
106117
f'No write support for this unknown log format "{suffix}"'
107118
) from None
108119

109-
@staticmethod
110-
def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]:
120+
@classmethod
121+
def compress(
122+
cls, filename: StringPathLike, **kwargs: Any
123+
) -> Tuple[Type[MessageWriter], FileLike]:
111124
"""
112125
Return the suffix and io object of the decompressed file.
113126
File will automatically recompress upon close.
@@ -117,12 +130,15 @@ def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]:
117130
raise ValueError(
118131
f"The file type {real_suffix} is currently incompatible with gzip."
119132
)
120-
if kwargs.get("append", False):
121-
mode = "ab" if real_suffix == ".blf" else "at"
133+
LoggerType = cls._get_logger_for_suffix(real_suffix)
134+
append = kwargs.get("append", False)
135+
136+
if issubclass(LoggerType, BinaryIOMessageWriter):
137+
mode = "ab" if append else "wb"
122138
else:
123-
mode = "wb" if real_suffix == ".blf" else "wt"
139+
mode = "at" if append else "wt"
124140

125-
return real_suffix, gzip.open(filename, mode)
141+
return LoggerType, gzip.open(filename, mode)
126142

127143
def on_message_received(self, msg: Message) -> None:
128144
pass

can/io/mf4.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ..message import Message
1515
from ..typechecking import StringPathLike
1616
from ..util import channel2int, dlc2len, len2dlc
17-
from .generic import FileIOMessageWriter, MessageReader
17+
from .generic import BinaryIOMessageReader, BinaryIOMessageWriter
1818

1919
logger = logging.getLogger("can.io.mf4")
2020

@@ -75,7 +75,7 @@
7575
CAN_ID_MASK = 0x1FFFFFFF
7676

7777

78-
class MF4Writer(FileIOMessageWriter):
78+
class MF4Writer(BinaryIOMessageWriter):
7979
"""Logs CAN data to an ASAM Measurement Data File v4 (.mf4).
8080
8181
MF4Writer does not support append mode.
@@ -265,7 +265,7 @@ def on_message_received(self, msg: Message) -> None:
265265
self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE)
266266

267267

268-
class MF4Reader(MessageReader):
268+
class MF4Reader(BinaryIOMessageReader):
269269
"""
270270
Iterator of CAN messages from a MF4 logging file.
271271

can/io/player.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .blf import BLFReader
1717
from .canutils import CanutilsLogReader
1818
from .csv import CSVReader
19-
from .generic import MessageReader
19+
from .generic import BinaryIOMessageReader, MessageReader
2020
from .mf4 import MF4Reader
2121
from .sqlite import SqliteReader
2222
from .trc import TRCReader
@@ -87,7 +87,13 @@ def __new__( # type: ignore
8787

8888
file_or_filename: AcceptedIOType = filename
8989
if suffix == ".gz":
90-
suffix, file_or_filename = LogReader.decompress(filename)
90+
ReaderType, file_or_filename = LogReader.decompress(filename)
91+
else:
92+
ReaderType = cls._get_logger_for_suffix(suffix)
93+
return ReaderType(file=file_or_filename, **kwargs)
94+
95+
@classmethod
96+
def _get_logger_for_suffix(cls, suffix: str) -> typing.Type[MessageReader]:
9197
try:
9298
ReaderType = LogReader.message_readers[suffix]
9399
except KeyError:
@@ -96,19 +102,22 @@ def __new__( # type: ignore
96102
) from None
97103
if ReaderType is None:
98104
raise ImportError(f"failed to import reader for extension {suffix}")
99-
return ReaderType(file=file_or_filename, **kwargs)
105+
return ReaderType
100106

101-
@staticmethod
107+
@classmethod
102108
def decompress(
109+
cls,
103110
filename: StringPathLike,
104-
) -> typing.Tuple[str, typing.Union[str, FileLike]]:
111+
) -> typing.Tuple[typing.Type[MessageReader], typing.Union[str, FileLike]]:
105112
"""
106113
Return the suffix and io object of the decompressed file.
107114
"""
108115
real_suffix = pathlib.Path(filename).suffixes[-2].lower()
109-
mode = "rb" if real_suffix == ".blf" else "rt"
116+
ReaderType = cls._get_logger_for_suffix(real_suffix)
117+
118+
mode = "rb" if issubclass(ReaderType, BinaryIOMessageReader) else "rt"
110119

111-
return real_suffix, gzip.open(filename, mode)
120+
return ReaderType, gzip.open(filename, mode)
112121

113122
def __iter__(self) -> typing.Generator[Message, None, None]:
114123
raise NotImplementedError()

can/io/trc.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
from ..message import Message
1717
from ..typechecking import StringPathLike
1818
from ..util import channel2int, dlc2len, len2dlc
19-
from .generic import FileIOMessageWriter, MessageReader
19+
from .generic import (
20+
TextIOMessageReader,
21+
TextIOMessageWriter,
22+
)
2023

2124
logger = logging.getLogger("can.io.trc")
2225

@@ -36,7 +39,7 @@ def __ge__(self, other):
3639
return NotImplemented
3740

3841

39-
class TRCReader(MessageReader):
42+
class TRCReader(TextIOMessageReader):
4043
"""
4144
Iterator of CAN messages from a TRC logging file.
4245
"""
@@ -241,7 +244,7 @@ def __iter__(self) -> Generator[Message, None, None]:
241244
self.stop()
242245

243246

244-
class TRCWriter(FileIOMessageWriter):
247+
class TRCWriter(TextIOMessageWriter):
245248
"""Logs CAN data to text file (.trc).
246249
247250
The measurement starts with the timestamp of the first registered message.

0 commit comments

Comments
 (0)