Passed
Push — main ( af1065...15d22f )
by Douglas
04:30
created

pocketutils.core.input_output.OpenMode.normalize()   B

Complexity

Conditions 7

Size

Total Lines 7
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 7
nop 1
dl 0
loc 7
rs 8
c 0
b 0
f 0
1
from __future__ import annotations
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
3
import abc
4
import contextlib
5
import logging
6
from pathlib import Path
7
from typing import Any, TypeVar
8
from urllib import request
9
10
from pocketutils.core import PathLike
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
introduced by
Cannot import 'pocketutils.core' due to syntax error 'invalid syntax (<unknown>, line 134)'
Loading history...
11
12
T = TypeVar("T", covariant=True)
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
13
Y = TypeVar("Y")
0 ignored issues
show
Coding Style Naming introduced by
Class name "Y" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
14
Z = TypeVar("Z")
0 ignored issues
show
Coding Style Naming introduced by
Class name "Z" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
15
logger = logging.getLogger("pocketutils")
16
17
18
class Writeable(metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
19
    @classmethod
20
    def isinstance(cls, value: Any):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
21
        return hasattr(value, "write") and hasattr(value, "flush") and hasattr(value, "close")
22
23
    def write(self, msg):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
24
        raise NotImplementedError()
25
26
    def flush(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
27
        raise NotImplementedError()
28
29
    def close(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
30
        raise NotImplementedError()
31
32
    def __enter__(self):
33
        return self
34
35
    def __exit__(self, exc_type, exc_value, traceback):
36
        self.close()
37
38
39
class DevNull(Writeable):
40
    """Pretends to write but doesn't."""
41
42
    def write(self, msg):
43
        pass
44
45
    def flush(self):
46
        pass
47
48
    def close(self):
49
        pass
50
51
    def __enter__(self):
52
        return self
53
54
    def __exit__(self, exc_type, exc_value, traceback):
55
        self.close()
56
57
58
class LogWriter:
59
    """
60
    A call to a logger at some level, pretending to be a writer.
61
    Has a write method, as well as flush and close methods that do nothing.
62
    """
63
64
    def __init__(self, level: int | str):
65
        if isinstance(level, str):
66
            level = level.upper()
67
        self.level = logging.getLevelName(level)
68
69
    def write(self, msg: str):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
70
        getattr(logger, self.level)(msg)
71
72
    def flush(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
73
        pass
74
75
    def close(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
76
        pass
77
78
    def __enter__(self):
79
        return self
80
81
    def __exit__(self, exc_type, exc_value, traceback):
82
        self.close()
83
84
85
class DelegatingWriter:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
86
    # we CANNOT override TextIOBase: It causes hangs
87
    def __init__(self, *writers):
88
        self._writers = writers
89
90
    def write(self, s):
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing function or method docstring
Loading history...
91
        for writer in self._writers:
92
            writer.write(s)
93
94
    def flush(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
95
        for writer in self._writers:
96
            writer.flush()
97
98
    def close(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
99
        for writer in self._writers:
100
            writer.close()
101
102
    def __enter__(self):
103
        return self
104
105
    def __exit__(self, exc_type, exc_value, traceback):
106
        self.close()
107
108
109
class Capture:
110
    """
111
    A lazy string-like object that wraps around a StringIO result.
112
    It's too hard to fully subclass a string while keeping it lazy.
113
    """
114
115
    def __init__(self, cio):
116
        self.__cio = cio
117
118
    @property
119
    def lines(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
120
        return self.split("\n")
121
122
    @property
123
    def value(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
124
        return self.__cio.getvalue()
125
126
    def __repr__(self):
127
        return self.__cio.getvalue()
128
129
    def __str__(self):
130
        return self.__cio.getvalue()
131
132
    def __len__(self):
133
        return len(repr(self))
134
135
    def split(self, x: str):
0 ignored issues
show
Coding Style Naming introduced by
Argument name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing function or method docstring
Loading history...
136
        return self.__cio.getvalue().split(x)
137
138
139
class OpenMode(str):
140
    """
141
    Python file open modes (``open()``-compatible).
142
    Contains method :meth:`normalize` and properties :meth:`read`.
143
144
    Here are the flags:
145
        - 'r' means read
146
        - 'w' means overwrite
147
        - 'x' means exclusive write; complain if it exists
148
        - 'a' means append
149
        - 't' means text (default)
150
        - 'b' means binary
151
        - '+' means open for updating
152
    """
153
154
    @property
155
    def read(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
156
        return "w" not in self and "x" not in self and "a" not in self
157
158
    @property
159
    def write(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
160
        return "w" in self or "x" in self or "a" in self
161
162
    @property
163
    def overwrite(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
164
        return "w" in self
165
166
    @property
167
    def safe(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
168
        return "x" in self
169
170
    @property
171
    def append(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
172
        return "a" in self
173
174
    @property
175
    def text(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
176
        return "b" not in self
177
178
    @property
179
    def binary(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
180
        return "b" in self
181
182
    def normalize(self) -> str:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
183
        s = self.replace("U", "")
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
184
        if "r" not in self and "w" not in self and "x" not in self and "a" not in self:
185
            s = "r" + self
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
186
        if "t" not in self and "b" not in self:
187
            s = "t" + self
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
188
        return s
189
190
    def __eq__(self, other):
191
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
192
            return self.normalize() == other.normalize()
193
        elif isinstance(other, str):
194
            return self.normalize() == OpenMode(other).normalize()
195
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
196
197
    def __ne__(self, other):
198
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
199
            return self.normalize() != other.normalize()
200
        elif isinstance(other, str):
201
            return self.normalize() != OpenMode(other).normalize()
202
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
203
204
    def __lt__(self, other):
205
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
206
            return self.normalize() < other.normalize()
207
        elif isinstance(other, str):
208
            return self.normalize() < OpenMode(other).normalize()
209
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
210
211
    def __gt__(self, other):
212
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
213
            return self.normalize() > other.normalize()
214
        elif isinstance(other, str):
215
            return self.normalize() > OpenMode(other).normalize()
216
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
217
218
    def __le__(self, other):
219
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
220
            return self.normalize() <= other.normalize()
221
        elif isinstance(other, str):
222
            return self.normalize() <= OpenMode(other).normalize()
223
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
224
225
    def __ge__(self, other):
226
        if isinstance(other, OpenMode):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
227
            return self.normalize() >= other.normalize()
228
        elif isinstance(other, str):
229
            return self.normalize() >= OpenMode(other).normalize()
230
        raise TypeError(f"Wrong type {type(other)} of '{other}'")
231
232
233
def null_context():
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
234
    yield
235
236
237
@contextlib.contextmanager
238
def silenced(no_stdout: bool = True, no_stderr: bool = True):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
239
    with contextlib.redirect_stdout(DevNull()) if no_stdout else null_context():
240
        with contextlib.redirect_stderr(DevNull()) if no_stderr else null_context():
241
            yield
242
243
244
def stream_download(url: str, path: PathLike):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
245
    with request.urlopen(url) as stream:
246
        with Path(path).open("wb") as out:
247
            data = stream.read(1024 * 1024)
248
            while data:
249
                out.write(data)
250
                data = data.read(1024 * 1024)
251
252
253
__all__ = [
254
    "Writeable",
255
    "DevNull",
256
    "LogWriter",
257
    "DelegatingWriter",
258
    "Capture",
259
    "OpenMode",
260
    "PathLike",
261
]
262