Skip to content

Commit fbb9972

Browse files
committed
can.io: Add preferred mode for opening files to Reader/Writer classes.
1 parent 6c820a1 commit fbb9972

File tree

10 files changed

+95
-32
lines changed

10 files changed

+95
-32
lines changed

can/io/asc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class ASCReader(MessageReader):
3232

3333
file: TextIO
3434

35+
PREFERRED_MODE: str = "rt"
36+
3537
def __init__(
3638
self,
3739
file: Union[StringPathLike, TextIO],
@@ -50,7 +52,7 @@ def __init__(
5052
`relative` (starting at 0.0) or `absolute` (starting at
5153
the system time). Default `True = relative`.
5254
"""
53-
super().__init__(file, mode="r")
55+
super().__init__(file)
5456

5557
if not self.file:
5658
raise ValueError("The given file cannot be None")
@@ -319,6 +321,8 @@ class ASCWriter(FileIOMessageWriter):
319321

320322
file: TextIO
321323

324+
PREFERRED_MODE: str = "wt"
325+
322326
FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}"
323327
FORMAT_MESSAGE_FD = " ".join(
324328
[
@@ -363,7 +367,7 @@ def __init__(
363367
f"{self.__class__.__name__} is currently not equipped to "
364368
f"append messages to an existing file."
365369
)
366-
super().__init__(file, mode="w")
370+
super().__init__(file)
367371

368372
self.channel = channel
369373

can/io/blf.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ class BLFReader(MessageReader):
142142

143143
file: BinaryIO
144144

145+
PREFERRED_MODE: str = "rb"
146+
145147
def __init__(
146148
self,
147149
file: Union[StringPathLike, BinaryIO],
@@ -152,7 +154,7 @@ def __init__(
152154
If this is a file-like object, is has to opened in binary
153155
read mode, not text read mode.
154156
"""
155-
super().__init__(file, mode="rb")
157+
super().__init__(file)
156158
data = self.file.read(FILE_HEADER_STRUCT.size)
157159
header = FILE_HEADER_STRUCT.unpack(data)
158160
if header[0] != b"LOGG":
@@ -361,6 +363,9 @@ class BLFWriter(FileIOMessageWriter):
361363

362364
file: BinaryIO
363365

366+
PREFERRED_MODE: str = "wb"
367+
PREFERRED_MODE_APPEND: str = "rb+"
368+
364369
#: Max log container size of uncompressed data
365370
max_container_size = 128 * 1024
366371

@@ -391,13 +396,13 @@ def __init__(
391396
Z_DEFAULT_COMPRESSION represents a default compromise between
392397
speed and compression (currently equivalent to level 6).
393398
"""
394-
mode = "rb+" if append else "wb"
399+
mode = self.PREFERRED_MODE_APPEND if append else self.PREFERRED_MODE
395400
try:
396401
super().__init__(file, mode=mode)
397402
except FileNotFoundError:
398403
# Trying to append to a non-existing file, create a new one
399404
append = False
400-
mode = "wb"
405+
mode = self.PREFERRED_MODE
401406
super().__init__(file, mode=mode)
402407
assert self.file is not None
403408
self.channel = channel

can/io/canutils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class CanutilsLogReader(MessageReader):
3535

3636
file: TextIO
3737

38+
PREFERRED_MODE: str = "rt"
39+
3840
def __init__(
3941
self,
4042
file: Union[StringPathLike, TextIO],
@@ -45,7 +47,7 @@ def __init__(
4547
If this is a file-like object, is has to opened in text
4648
read mode, not binary read mode.
4749
"""
48-
super().__init__(file, mode="r")
50+
super().__init__(file)
4951

5052
def __iter__(self) -> Generator[Message, None, None]:
5153
for line in self.file:
@@ -131,6 +133,9 @@ class CanutilsLogWriter(FileIOMessageWriter):
131133
It the first message does not have a timestamp, it is set to zero.
132134
"""
133135

136+
PREFERRED_MODE: str = "wt"
137+
PREFERRED_MODE_APPEND: str = "at"
138+
134139
def __init__(
135140
self,
136141
file: Union[StringPathLike, TextIO],
@@ -147,7 +152,7 @@ def __init__(
147152
:param bool append: if set to `True` messages are appended to
148153
the file, else the file is truncated
149154
"""
150-
mode = "a" if append else "w"
155+
mode = self.PREFERRED_MODE_APPEND if append else self.PREFERRED_MODE
151156
super().__init__(file, mode=mode)
152157

153158
self.channel = channel

can/io/csv.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class CSVReader(MessageReader):
2929

3030
file: TextIO
3131

32+
PREFERRED_MODE: str = "rt"
33+
3234
def __init__(
3335
self,
3436
file: Union[StringPathLike, TextIO],
@@ -39,7 +41,7 @@ def __init__(
3941
If this is a file-like object, is has to opened in text
4042
read mode, not binary read mode.
4143
"""
42-
super().__init__(file, mode="r")
44+
super().__init__(file)
4345

4446
def __iter__(self) -> Generator[Message, None, None]:
4547
# skip the header line
@@ -90,6 +92,9 @@ class CSVWriter(FileIOMessageWriter):
9092

9193
file: TextIO
9294

95+
PREFERRED_MODE: str = "wt"
96+
PREFERRED_MODE_APPEND: str = "at"
97+
9398
def __init__(
9499
self,
95100
file: Union[StringPathLike, TextIO],
@@ -105,7 +110,7 @@ def __init__(
105110
the file is truncated and starts with a newly
106111
written header line
107112
"""
108-
mode = "a" if append else "w"
113+
mode = self.PREFERRED_MODE_APPEND if append else self.PREFERRED_MODE
109114
super().__init__(file, mode=mode)
110115

111116
# Write a header row

can/io/generic.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import (
66
Any,
77
ContextManager,
8+
Final,
89
Iterable,
910
Optional,
1011
Type,
@@ -23,17 +24,28 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta):
2324
2425
Can be used as a context manager.
2526
27+
:const PREFERRED_MODE:
28+
the preferred mode to be used to open a file. Is used by default
29+
when no mode is specified for `__init__`.
30+
:const PREFERRED_MODE_APPEND:
31+
the preferred mode to be used to open a file in append mode. Is
32+
used by default when no mode is specified for `__init__` and if
33+
`__init__` takes an boolean `append` argument which has been
34+
specified as `True`.
2635
:attr file:
2736
the file-like object that is kept internally, or `None` if none
2837
was opened
2938
"""
3039

3140
file: Optional[typechecking.FileLike]
3241

42+
PREFERRED_MODE_APPEND: str = "rt+"
43+
PREFERRED_MODE: str = "rt"
44+
3345
def __init__(
3446
self,
3547
file: Optional[typechecking.AcceptedIOType],
36-
mode: str = "rt",
48+
mode: Optional[str] = None,
3749
**kwargs: Any,
3850
) -> None:
3951
"""
@@ -46,6 +58,8 @@ def __init__(
4658
# file is None or some file-like object
4759
self.file = cast(Optional[typechecking.FileLike], file)
4860
else:
61+
if mode is None:
62+
mode = self.PREFERRED_MODE
4963
encoding: Optional[str] = (
5064
None
5165
if "b" in mode
@@ -85,20 +99,22 @@ class MessageWriter(BaseIOHandler, Listener, metaclass=ABCMeta):
8599

86100
file: Optional[typechecking.FileLike]
87101

102+
PREFERRED_MODE: str = "wt"
103+
88104

89105
class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta):
90106
"""A specialized base class for all writers with file descriptors."""
91107

92108
file: typechecking.FileLike
93109

94-
def __init__(
95-
self, file: typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any
96-
) -> None:
110+
PREFERRED_MODE: str = "wt"
111+
112+
def __init__(self, file: typechecking.AcceptedIOType, **kwargs: Any) -> None:
97113
# Not possible with the type signature, but be verbose for user-friendliness
98114
if file is None:
99115
raise ValueError("The given file cannot be None")
100116

101-
super().__init__(file, mode, **kwargs)
117+
super().__init__(file, **kwargs)
102118

103119
def file_size(self) -> int:
104120
"""Return an estimate of the current file size in bytes."""

can/io/logger.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,28 @@ def __new__( # type: ignore
9494

9595
file_or_filename: AcceptedIOType = filename
9696
if suffix == ".gz":
97-
suffix, file_or_filename = Logger.compress(filename, **kwargs)
97+
LoggerType, file_or_filename = Logger.compress(filename, **kwargs)
98+
else:
99+
LoggerType = cls._get_logger_for_suffix(suffix)
100+
101+
return LoggerType(file=file_or_filename, **kwargs)
98102

103+
@classmethod
104+
def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]:
99105
try:
100106
LoggerType = Logger.message_writers[suffix]
101107
if LoggerType is None:
102108
raise ValueError(f'failed to import logger for extension "{suffix}"')
103-
return LoggerType(file=file_or_filename, **kwargs)
109+
return LoggerType
104110
except KeyError:
105111
raise ValueError(
106112
f'No write support for this unknown log format "{suffix}"'
107113
) from None
108114

109-
@staticmethod
110-
def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]:
115+
@classmethod
116+
def compress(
117+
cls, filename: StringPathLike, **kwargs: Any
118+
) -> Tuple[Type[MessageWriter], FileLike]:
111119
"""
112120
Return the suffix and io object of the decompressed file.
113121
File will automatically recompress upon close.
@@ -117,12 +125,13 @@ def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]:
117125
raise ValueError(
118126
f"The file type {real_suffix} is currently incompatible with gzip."
119127
)
128+
LoggerType = cls._get_logger_for_suffix(real_suffix)
120129
if kwargs.get("append", False):
121-
mode = "ab" if real_suffix == ".blf" else "at"
130+
mode = LoggerType.PREFERRED_MODE_APPEND
122131
else:
123-
mode = "wb" if real_suffix == ".blf" else "wt"
132+
mode = LoggerType.PREFERRED_MODE
124133

125-
return real_suffix, gzip.open(filename, mode)
134+
return LoggerType, gzip.open(filename, mode)
126135

127136
def on_message_received(self, msg: Message) -> None:
128137
pass

can/io/mf4.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class MF4Writer(FileIOMessageWriter):
8585
It the first message does not have a timestamp, it is set to zero.
8686
"""
8787

88+
PREFERRED_MODE: str = "w+b"
89+
8890
def __init__(
8991
self,
9092
file: Union[StringPathLike, BinaryIO],
@@ -117,7 +119,7 @@ def __init__(
117119
f"append messages to an existing file."
118120
)
119121

120-
super().__init__(file, mode="w+b")
122+
super().__init__(file)
121123
now = datetime.now()
122124
self._mdf = cast(MDF4, MDF(version="4.10"))
123125
self._mdf.header.start_time = now
@@ -272,6 +274,8 @@ class MF4Reader(MessageReader):
272274
The MF4Reader only supports MF4 files that were recorded with python-can.
273275
"""
274276

277+
PREFERRED_MODE: str = "rb"
278+
275279
def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
276280
"""
277281
:param file: a path-like object or as file-like object to read from
@@ -284,7 +288,7 @@ def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None:
284288
"the optional dependency [mf4] to use the MF4Reader."
285289
)
286290

287-
super().__init__(file, mode="rb")
291+
super().__init__(file)
288292

289293
self._mdf: MDF4
290294
if isinstance(file, BufferedIOBase):

can/io/player.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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,21 @@ 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+
mode = ReaderType.PREFERRED_MODE
110118

111-
return real_suffix, gzip.open(filename, mode)
119+
return ReaderType, gzip.open(filename, mode)
112120

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

can/io/printer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class Printer(MessageWriter):
2424

2525
file: Optional[TextIO]
2626

27+
PREFERRED_MODE: str = "wt"
28+
PREFERRED_MODE_APPEND: str = "at"
29+
2730
def __init__(
2831
self,
2932
file: Optional[Union[StringPathLike, TextIO]] = None,
@@ -39,7 +42,7 @@ def __init__(
3942
else the file is truncated
4043
"""
4144
self.write_to_file = file is not None
42-
mode = "a" if append else "w"
45+
mode = self.PREFERRED_MODE_APPEND if append else self.PREFERRED_MODE
4346
super().__init__(file, mode=mode)
4447

4548
def on_message_received(self, msg: Message) -> None:

0 commit comments

Comments
 (0)