zstdlib 0.0.7__tar.gz → 0.0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zstdlib-0.0.7 → zstdlib-0.0.11}/PKG-INFO +3 -3
- {zstdlib-0.0.7 → zstdlib-0.0.11}/pyproject.toml +3 -3
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/log/test_cute.py +22 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/test_enum.py +4 -4
- zstdlib-0.0.11/tests/test_io.py +90 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/test_singleton.py +2 -2
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/__init__.py +2 -2
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/ansi.py +6 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/enum.py +1 -5
- zstdlib-0.0.11/zstdlib/io.py +238 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/log/cute.py +33 -25
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/log/trace.py +1 -3
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/singleton.py +2 -2
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib.egg-info/PKG-INFO +3 -3
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib.egg-info/SOURCES.txt +2 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/LICENSE +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/README.md +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/setup.cfg +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/__init__.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/log/__init__.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/log/base.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/log/test_trace.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/not_set.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/test_ansi.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/tests/test_frozen.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/frozen.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/log/__init__.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/not_set.py +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib/py.typed +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib.egg-info/dependency_links.txt +0 -0
- {zstdlib-0.0.7 → zstdlib-0.0.11}/zstdlib.egg-info/top_level.txt +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: zstdlib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: A set of useful python utilities
|
|
5
5
|
License: GPLv3
|
|
6
6
|
Project-URL: Homepage, https://github.com/zwimer/zstdlib
|
|
7
7
|
Keywords: remote,pipe
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
name = "zstdlib"
|
|
7
7
|
classifiers = [
|
|
8
8
|
"Programming Language :: Python :: 3",
|
|
9
|
-
"Programming Language :: Python :: 3.
|
|
9
|
+
"Programming Language :: Python :: 3.12",
|
|
10
10
|
"Development Status :: 5 - Production/Stable",
|
|
11
11
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
12
12
|
]
|
|
@@ -14,7 +14,7 @@ keywords = ["remote", "pipe"]
|
|
|
14
14
|
license = {text = "GPLv3"}
|
|
15
15
|
description = "A set of useful python utilities"
|
|
16
16
|
urls = {Homepage = "https://github.com/zwimer/zstdlib"}
|
|
17
|
-
requires-python = ">= 3.
|
|
17
|
+
requires-python = ">= 3.12"
|
|
18
18
|
# dependencies = [
|
|
19
19
|
# # Client
|
|
20
20
|
# "zstandard[cffi]",
|
|
@@ -63,7 +63,7 @@ disable = [
|
|
|
63
63
|
|
|
64
64
|
[tool.black]
|
|
65
65
|
line-length = 110
|
|
66
|
-
target-version = ["
|
|
66
|
+
target-version = ["py312", "py313"]
|
|
67
67
|
|
|
68
68
|
[tool.ruff]
|
|
69
69
|
line-length = 110
|
|
@@ -78,6 +78,28 @@ class TestCuteFormatter(LeftBase, unittest.TestCase):
|
|
|
78
78
|
self.assertEqual(Color.bright_red_bg_yellow("CRITICAL".ljust(8)), l_name(5))
|
|
79
79
|
self.assertEqual(Color.bright_red_bg_yellow("Level 60".ljust(8)), l_name(6))
|
|
80
80
|
|
|
81
|
+
def test_custom_dim_level(self) -> None:
|
|
82
|
+
name = "TestCuteFormatter.test_dim_level"
|
|
83
|
+
msg = "test"
|
|
84
|
+
with self.hijack(name, fmt=CuteFormatter(dim_level=5)) as log:
|
|
85
|
+
log.log(4, msg)
|
|
86
|
+
log.log(5, msg)
|
|
87
|
+
self.assertEqual(2, len(self.messages[log]))
|
|
88
|
+
l_name = lambda idx: self.messages[log][idx].split("|")[0].strip()
|
|
89
|
+
self.assertEqual(Color.dim("Level 4".ljust(8)), l_name(0))
|
|
90
|
+
self.assertEqual("Level 5", l_name(1))
|
|
91
|
+
|
|
92
|
+
def test_custom_level_color(self) -> None:
|
|
93
|
+
name = "TestCuteFormatter.test_custom_level_color"
|
|
94
|
+
msg = "test"
|
|
95
|
+
with self.hijack(name, fmt=CuteFormatter(level_colors={15: Color.bright_red_bg_yellow})) as log:
|
|
96
|
+
log.log(14, msg)
|
|
97
|
+
log.log(15, msg)
|
|
98
|
+
self.assertEqual(2, len(self.messages[log]))
|
|
99
|
+
l_name = lambda idx: self.messages[log][idx].split("|")[0].strip()
|
|
100
|
+
self.assertEqual("Level 14".ljust(8), l_name(0))
|
|
101
|
+
self.assertEqual(Color.bright_red_bg_yellow("Level 15".ljust(8)), l_name(1))
|
|
102
|
+
|
|
81
103
|
def test_exception(self) -> None:
|
|
82
104
|
name = "TestCuteFormatter.test_exception"
|
|
83
105
|
with self.hijack(name, fmt=CuteFormatter()) as log:
|
|
@@ -63,9 +63,9 @@ class TestEnumType(unittest.TestCase):
|
|
|
63
63
|
class ET10(metaclass=EnumType):
|
|
64
64
|
arg1: int = 0
|
|
65
65
|
|
|
66
|
-
with self.assertRaises(
|
|
66
|
+
with self.assertRaises(TypeError):
|
|
67
67
|
ET10()
|
|
68
|
-
with self.assertRaises(
|
|
68
|
+
with self.assertRaises(TypeError):
|
|
69
69
|
ET10.__init__({})
|
|
70
70
|
|
|
71
71
|
|
|
@@ -127,9 +127,9 @@ class TestEnum(unittest.TestCase):
|
|
|
127
127
|
class E10(Enum):
|
|
128
128
|
arg1: int = 0
|
|
129
129
|
|
|
130
|
-
with self.assertRaises(
|
|
130
|
+
with self.assertRaises(TypeError):
|
|
131
131
|
E10()
|
|
132
|
-
with self.assertRaises(
|
|
132
|
+
with self.assertRaises(TypeError):
|
|
133
133
|
E10.__init__({})
|
|
134
134
|
|
|
135
135
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from zstdlib.io import ProtectedFile, BinaryIO, TextIO, io
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RamFile(tempfile.SpooledTemporaryFile):
|
|
8
|
+
"""
|
|
9
|
+
A file-like object that stores its contents in memory.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
rollover = None # type: ignore
|
|
13
|
+
|
|
14
|
+
def __init__(self, *args, mode: str = "w+", **kwargs):
|
|
15
|
+
super().__init__(10**9, *args, mode=mode, **kwargs)
|
|
16
|
+
|
|
17
|
+
def load(self, x):
|
|
18
|
+
self.write(x)
|
|
19
|
+
self.flush()
|
|
20
|
+
self.seek(self.tell() - len(x))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestIO(unittest.TestCase):
|
|
24
|
+
|
|
25
|
+
def test_init(self):
|
|
26
|
+
with RamFile(mode="wb+") as fb, RamFile() as fs:
|
|
27
|
+
ts = TextIO(fs)
|
|
28
|
+
tb = BinaryIO(fb)
|
|
29
|
+
with self.assertRaises(TypeError):
|
|
30
|
+
TextIO(fb)
|
|
31
|
+
with self.assertRaises(TypeError):
|
|
32
|
+
BinaryIO(fs)
|
|
33
|
+
self.assertIs(ts, io(fs))
|
|
34
|
+
self.assertIs(tb, io(fb))
|
|
35
|
+
|
|
36
|
+
def test_protected_file(self):
|
|
37
|
+
with RamFile() as f:
|
|
38
|
+
p = ProtectedFile(f)
|
|
39
|
+
self.assertIs(f, p.raw)
|
|
40
|
+
self.assertIs(f.mode, p.mode)
|
|
41
|
+
for i in ("read", "readline", "readlines", "readall", "seek"):
|
|
42
|
+
with self.assertRaises(AttributeError):
|
|
43
|
+
getattr(p, i)
|
|
44
|
+
|
|
45
|
+
def test_unread(self):
|
|
46
|
+
with RamFile() as raw:
|
|
47
|
+
f = io(raw)
|
|
48
|
+
f.unread("hello")
|
|
49
|
+
self.assertEqual("hello", f._buffer)
|
|
50
|
+
|
|
51
|
+
def test_read(self):
|
|
52
|
+
with RamFile() as raw:
|
|
53
|
+
raw.load("foobaz")
|
|
54
|
+
f = io(raw)
|
|
55
|
+
# Test read of file
|
|
56
|
+
self.assertEqual("fo", f.read(2))
|
|
57
|
+
self.assertEqual("o", f.read(1))
|
|
58
|
+
# Test read of buffer
|
|
59
|
+
f.unread("bar")
|
|
60
|
+
self.assertEqual("b", f.read(1))
|
|
61
|
+
# Test read of file + buffer
|
|
62
|
+
f.unread("foob")
|
|
63
|
+
self.assertEqual("foobar", f.read(6))
|
|
64
|
+
f.unread("foobar")
|
|
65
|
+
self.assertEqual("foobarbaz", f.read())
|
|
66
|
+
|
|
67
|
+
def test_peek(self):
|
|
68
|
+
with RamFile() as raw:
|
|
69
|
+
f = io(raw)
|
|
70
|
+
f.unread("foo")
|
|
71
|
+
# Test peek of buffer
|
|
72
|
+
for i in range(3):
|
|
73
|
+
self.assertEqual("fo", f.peek(2))
|
|
74
|
+
self.assertEqual("foo", f.read())
|
|
75
|
+
# Test peek of file
|
|
76
|
+
raw.load("bar")
|
|
77
|
+
for i in range(3):
|
|
78
|
+
self.assertEqual("ba", f.peek(2))
|
|
79
|
+
self.assertEqual("bar", f.read())
|
|
80
|
+
# Test peek of both
|
|
81
|
+
f.unread("baz")
|
|
82
|
+
raw.load("qux")
|
|
83
|
+
for i in range(3):
|
|
84
|
+
for k in range(5):
|
|
85
|
+
self.assertEqual("bazqu"[:k], f.peek(k))
|
|
86
|
+
self.assertEqual("bazqux", f.read())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
unittest.main()
|
|
@@ -23,7 +23,7 @@ class TestSingletonType(unittest.TestCase):
|
|
|
23
23
|
class ST3(metaclass=SingletonType):
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
|
-
with self.assertRaises(
|
|
26
|
+
with self.assertRaises(TypeError):
|
|
27
27
|
|
|
28
28
|
class ST4(ST3):
|
|
29
29
|
pass
|
|
@@ -116,7 +116,7 @@ class TestSingleton(unittest.TestCase):
|
|
|
116
116
|
class S3(Singleton):
|
|
117
117
|
pass
|
|
118
118
|
|
|
119
|
-
with self.assertRaises(
|
|
119
|
+
with self.assertRaises(TypeError):
|
|
120
120
|
|
|
121
121
|
class S4(S3):
|
|
122
122
|
pass
|
|
@@ -107,6 +107,12 @@ class Color(metaclass=_ColorMeta):
|
|
|
107
107
|
raise TypeError("Cannot add non-Color to Color")
|
|
108
108
|
return type(self)(code=f"{self.code[:-1]};{other.code[len(_PREFIX):]}")
|
|
109
109
|
|
|
110
|
+
def __eq__(self, other: object) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
:return: Whether the two colors are equal
|
|
113
|
+
"""
|
|
114
|
+
return isinstance(other, type(self)) and self.code == other.code
|
|
115
|
+
|
|
110
116
|
def __repr__(self) -> str:
|
|
111
117
|
"""
|
|
112
118
|
:return: The ansi color code this object represents
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def _not_implemented(*_, **__):
|
|
5
|
-
raise NotImplementedError("Cannot instantiate this class")
|
|
6
|
-
|
|
7
|
-
|
|
8
4
|
class EnumType(type):
|
|
9
5
|
"""
|
|
10
6
|
Metaclass for uninstantiable Enum classes with required annotations and unique values
|
|
@@ -17,7 +13,7 @@ class EnumType(type):
|
|
|
17
13
|
for bad in ("__init__", "__new__"):
|
|
18
14
|
if bad in attrs:
|
|
19
15
|
raise AttributeError("Cannot define __init__ or __new__")
|
|
20
|
-
attrs[bad] =
|
|
16
|
+
attrs[bad] = None
|
|
21
17
|
# Check annotations
|
|
22
18
|
public = {i: k for i, k in attrs.items() if not i.startswith("__")}
|
|
23
19
|
annotations = attrs.get("__annotations__", {})
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from io import (
|
|
2
|
+
IOBase,
|
|
3
|
+
TextIOBase as _PyRawTextIOBase,
|
|
4
|
+
RawIOBase as _PyRawIOBase,
|
|
5
|
+
BufferedIOBase as _PyBufferedIOBase,
|
|
6
|
+
)
|
|
7
|
+
from threading import RLock
|
|
8
|
+
from itertools import chain
|
|
9
|
+
from typing import Self
|
|
10
|
+
import weakref
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ("TextIO", "BinaryIO", "io")
|
|
14
|
+
|
|
15
|
+
type _BinaryBase = _PyRawIOBase | _PyBufferedIOBase
|
|
16
|
+
type _TextBase = _PyRawTextIOBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProtectedFile:
|
|
20
|
+
"""
|
|
21
|
+
A wrapper around an IO object that prevents access to certain attributes
|
|
22
|
+
If absolutely necessary, users can access the raw object directly via .raw
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
PROTECTED_PREFIXES = ("read", "seek", "getbuffer", "getvalue", "detach")
|
|
26
|
+
__slots__ = ("raw",)
|
|
27
|
+
|
|
28
|
+
def __init__(self, raw: IOBase) -> None:
|
|
29
|
+
"""
|
|
30
|
+
:param raw: The raw IO object to wrap, users can access it directly if they must
|
|
31
|
+
"""
|
|
32
|
+
self.raw: IOBase = raw
|
|
33
|
+
|
|
34
|
+
def __getattr__(self, item):
|
|
35
|
+
"""
|
|
36
|
+
Prevent access to certain attributes
|
|
37
|
+
"""
|
|
38
|
+
if any(item.startswith(i) for i in self.PROTECTED_PREFIXES):
|
|
39
|
+
raise AttributeError(f"Attribute {item} is protected")
|
|
40
|
+
return getattr(self.raw, item)
|
|
41
|
+
|
|
42
|
+
def __hash__(self) -> int:
|
|
43
|
+
return hash(self.raw)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# See bug: https://github.com/pylint-dev/pylint/issues/9335
|
|
47
|
+
# pylint: disable=undefined-variable
|
|
48
|
+
class _IOWrapperBase[T: (str, bytes)]:
|
|
49
|
+
"""
|
|
50
|
+
A base class for IO objects that adds additional functionality such as readuntil, unread, and peek
|
|
51
|
+
These classes are singletons with respect to their input object
|
|
52
|
+
Note: This class takes ownership of the input object, do not use it elsewhere
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("lock", "f", "_buffer", "_eof")
|
|
56
|
+
_instances: weakref.WeakKeyDictionary[IOBase, Self] = weakref.WeakKeyDictionary()
|
|
57
|
+
_wr_lock = RLock()
|
|
58
|
+
|
|
59
|
+
def __new__(cls, f: IOBase, binary: bool) -> Self:
|
|
60
|
+
"""
|
|
61
|
+
Create a new instance of cls for raw, or return an existing instance if one exists
|
|
62
|
+
"""
|
|
63
|
+
with cls._wr_lock:
|
|
64
|
+
if (ret := cls._instances.get(f, None)) is None:
|
|
65
|
+
cls._instances[f] = (ret := super().__new__(cls))
|
|
66
|
+
return ret
|
|
67
|
+
|
|
68
|
+
def __init__(self, f: IOBase, *, binary: bool) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize the IO object with a raw IO object
|
|
71
|
+
Note: This class takes ownership of the input object, do not use it elsewhere
|
|
72
|
+
:param f: The raw IO object to wrap
|
|
73
|
+
:param binary: If True, the IO object will read/write bytes, otherwise it will read/write strings
|
|
74
|
+
"""
|
|
75
|
+
self._buffer: T = (bytes if binary else str)() # type: ignore
|
|
76
|
+
self.f = ProtectedFile(f)
|
|
77
|
+
self.lock = RLock()
|
|
78
|
+
|
|
79
|
+
def read(self, size: int = -1) -> T:
|
|
80
|
+
"""
|
|
81
|
+
Read up to size characters from self.raw or until EOF
|
|
82
|
+
If size is unspecified or -1, read until EOF
|
|
83
|
+
"""
|
|
84
|
+
if size == 0:
|
|
85
|
+
return self._buffer[:0]
|
|
86
|
+
with self.lock:
|
|
87
|
+
ret = self._read_buffer(size)
|
|
88
|
+
if len(ret) == size:
|
|
89
|
+
return ret
|
|
90
|
+
ret += self._read(-1 if size == -1 else (size - len(ret)))
|
|
91
|
+
return ret
|
|
92
|
+
|
|
93
|
+
def readline(self) -> T:
|
|
94
|
+
"""
|
|
95
|
+
Read a line from self.raw or until EOF
|
|
96
|
+
Newline is included in the return value
|
|
97
|
+
"""
|
|
98
|
+
with self.lock:
|
|
99
|
+
nl: T = "\n" if isinstance(self._buffer, str) else b"\n"
|
|
100
|
+
ret = self._read_buffer(self._buffer.find(nl)) + self._read_buffer(1)
|
|
101
|
+
if ret.endswith(nl):
|
|
102
|
+
return ret
|
|
103
|
+
return ret + self.f.raw.readline() # type: ignore
|
|
104
|
+
|
|
105
|
+
def readlines(self) -> list[T]:
|
|
106
|
+
"""
|
|
107
|
+
Read all lines from self.raw until EOF
|
|
108
|
+
"""
|
|
109
|
+
with self.lock:
|
|
110
|
+
ret: list[T] = []
|
|
111
|
+
while add := self.readline():
|
|
112
|
+
ret.append(add)
|
|
113
|
+
return ret
|
|
114
|
+
|
|
115
|
+
def peek(self, size: int = -1) -> T:
|
|
116
|
+
"""
|
|
117
|
+
Read size characters from self.raw or until EOF, without consuming the data
|
|
118
|
+
"""
|
|
119
|
+
with self.lock:
|
|
120
|
+
self.unread(ret := self.read(size))
|
|
121
|
+
return ret
|
|
122
|
+
|
|
123
|
+
def unread(self, data: T) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Unread data back to the internal buffer
|
|
126
|
+
"""
|
|
127
|
+
with self.lock:
|
|
128
|
+
self._buffer = data + self._buffer
|
|
129
|
+
|
|
130
|
+
def readuntil(self, until: T, *, eof_ok: bool = True) -> T:
|
|
131
|
+
"""
|
|
132
|
+
Read until until is found in f. If eof_ok is False, raise EOFError if until is not found before EOF.
|
|
133
|
+
:param until: The str/bytes to read until
|
|
134
|
+
:param inclusive: If True, the returned value will end with until
|
|
135
|
+
:param eof_ok: if False, raise EOFError if until is not found before EOF
|
|
136
|
+
:return: The data read from self.raw, up to and including until
|
|
137
|
+
"""
|
|
138
|
+
if isinstance(until, str) ^ isinstance(self._buffer, str):
|
|
139
|
+
raise TypeError("until: expected same type as buffer")
|
|
140
|
+
if len(until) == 0:
|
|
141
|
+
raise ValueError("until may not be empty")
|
|
142
|
+
with self.lock:
|
|
143
|
+
if until in self._buffer:
|
|
144
|
+
return self._read_buffer(self._buffer.find(until) + len(until))
|
|
145
|
+
buffer = [self._read_buffer()]
|
|
146
|
+
# Use bytearray to avoid multiple concatenations
|
|
147
|
+
# Read until until is found, avoid small read() syscall's because they are slow
|
|
148
|
+
while (n := self._remainder(buffer, until)) and (add := self._read(n)):
|
|
149
|
+
buffer.append(add)
|
|
150
|
+
ret = buffer[0][:0].join(buffer)
|
|
151
|
+
if not ret.endswith(until) and not eof_ok:
|
|
152
|
+
raise EOFError("until: not found before EOF")
|
|
153
|
+
return ret
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
# Helper functions
|
|
157
|
+
#
|
|
158
|
+
|
|
159
|
+
def _read(self, size) -> T:
|
|
160
|
+
ret: T = self.f.raw.read(size) # type: ignore
|
|
161
|
+
return ret if ret else self._buffer[:0] # Some .read's can return None
|
|
162
|
+
|
|
163
|
+
def _read_buffer(self, size: int = -1) -> T:
|
|
164
|
+
if size == -1:
|
|
165
|
+
size = len(self._buffer)
|
|
166
|
+
ret: T = self._buffer[:size]
|
|
167
|
+
self._buffer = self._buffer[size:]
|
|
168
|
+
return ret
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _remainder(data: list[T], suffix: T) -> int:
|
|
172
|
+
assert suffix, "suffix: expected non-empty str or bytes"
|
|
173
|
+
rev = chain.from_iterable(reversed(i) for i in reversed(data))
|
|
174
|
+
for index, (i, k) in enumerate(zip(rev, reversed(suffix))):
|
|
175
|
+
if i != k:
|
|
176
|
+
return len(suffix) - index
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
#
|
|
180
|
+
# Protection
|
|
181
|
+
#
|
|
182
|
+
|
|
183
|
+
def __hash__(self) -> int:
|
|
184
|
+
return hash((self.__class__, self.f))
|
|
185
|
+
|
|
186
|
+
__deepcopy__ = None # type: ignore
|
|
187
|
+
__copy__ = None # type: ignore
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _mode(f: IOBase, binary: bool | None) -> bool:
|
|
191
|
+
mode = getattr(f, "mode", "")
|
|
192
|
+
binary = binary or "b" in mode or isinstance(f, (_PyRawIOBase, _PyBufferedIOBase))
|
|
193
|
+
text = (binary is False) or (mode and "b" not in mode) or isinstance(f, _PyRawTextIOBase)
|
|
194
|
+
if not text and not binary:
|
|
195
|
+
raise TypeError("Cannot determine if IO object is text or binary")
|
|
196
|
+
if text and binary:
|
|
197
|
+
raise TypeError("IO object is both text and binary")
|
|
198
|
+
return text
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TextIO(_IOWrapperBase[str]):
|
|
202
|
+
"""
|
|
203
|
+
A wrapper around TextIO objects that add additional functionality such as readuntil, unread, and peek
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __new__(cls, f: IOBase):
|
|
207
|
+
if not _mode(f, binary=False):
|
|
208
|
+
raise TypeError("raw should be a non-binary IO object")
|
|
209
|
+
return super().__new__(cls, f, binary=False)
|
|
210
|
+
|
|
211
|
+
def __init__(self, f: IOBase):
|
|
212
|
+
super().__init__(f, binary=False)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class BinaryIO(_IOWrapperBase[bytes]):
|
|
216
|
+
"""
|
|
217
|
+
A wrapper around BinaryIO objects that add additional functionality such as readuntil, unread, and peek
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __new__(cls, f: IOBase):
|
|
221
|
+
if _mode(f, binary=True):
|
|
222
|
+
raise TypeError("raw should be a binary IO object")
|
|
223
|
+
return super().__new__(cls, f, binary=True)
|
|
224
|
+
|
|
225
|
+
def __init__(self, f: IOBase):
|
|
226
|
+
super().__init__(f, binary=False)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def io(raw: IOBase, binary: bool | None = None) -> TextIO | BinaryIO:
|
|
230
|
+
"""
|
|
231
|
+
Convert an IO object to a TextIO or BinaryIO object
|
|
232
|
+
For raw to be valid, one of the three must be true:
|
|
233
|
+
1. raw is an io.TextIOBase, io.RawIOBase, or io.BufferedIOBase object
|
|
234
|
+
2. raw has a .mode string attribute
|
|
235
|
+
3. binary must be set
|
|
236
|
+
If the three detection methods disagree on if raw is binary, a TypeError is raised
|
|
237
|
+
"""
|
|
238
|
+
return TextIO(raw) if _mode(raw, binary) else BinaryIO(raw)
|
|
@@ -16,17 +16,25 @@ class CuteFormatter(Formatter):
|
|
|
16
16
|
A log formatter that can print log messages with colors.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
__slots__ = ("colored", "_color", "_cmap", "_lvl_cmap", "_dim_level", "_name_width")
|
|
20
|
+
DEFAULT_CUTE_WIDTHS: dict[str, int] = {"cute_levelname": 8, "cute_time": 23, "cute_name": 12}
|
|
21
|
+
DEFAULT_LEVEL_COLORS: dict[int, Color] = {
|
|
22
|
+
INFO: Color.blue,
|
|
23
|
+
WARNING: Color.yellow,
|
|
24
|
+
ERROR: Color.red,
|
|
25
|
+
CRITICAL: Color.bright_red_bg_yellow,
|
|
26
|
+
}
|
|
21
27
|
|
|
22
|
-
# pylint: disable=dangerous-default-value
|
|
28
|
+
# pylint: disable=dangerous-default-value,keyword-arg-before-vararg,too-many-arguments
|
|
23
29
|
def __init__(
|
|
24
30
|
self,
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
fmt="%(cute_levelname)s | %(cute_asctime)s | %(cute_name)s | %(cute_message)s",
|
|
32
|
+
*args,
|
|
27
33
|
colored: bool = True,
|
|
34
|
+
dim_level: int = DEBUG,
|
|
35
|
+
colors: dict[str, Color] = {},
|
|
36
|
+
level_colors: dict[int, Color] = {},
|
|
28
37
|
cute_widths: dict[str, int] = {},
|
|
29
|
-
fmt="%(cute_levelname)s | %(cute_asctime)s | %(cute_name)s | %(cute_message)s",
|
|
30
38
|
**kwargs,
|
|
31
39
|
):
|
|
32
40
|
"""
|
|
@@ -34,16 +42,20 @@ class CuteFormatter(Formatter):
|
|
|
34
42
|
Do not modify cute_ parameters in the fmt string; they might have special formatting that is not visible
|
|
35
43
|
Ex. %(cute_name)-8s should be done via cute_widths["cute_name"] = 8
|
|
36
44
|
This is because cute_widths takes into account 0-width color codes when padding
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
Level colors used are: DEFAULT_LEVEL_COLORS | level_colors
|
|
46
|
+
Dimming of log messages is done for levels < dim_level
|
|
39
47
|
:param fmt: The format string to use for the log message; do not modify cute_ parameters
|
|
48
|
+
:param args: Passed to logging.Formatter
|
|
49
|
+
:param colored: If False, no colors will be used regardless of the colors parameter
|
|
50
|
+
:param colors: The colors to use for the given loggers, automatic for non-specified loggers
|
|
51
|
+
:param level_colors: The colors to use >= the given levels, automatic for non-specified loggers
|
|
40
52
|
:param cute_widths: The widths of the cute_ columns in the log message
|
|
41
53
|
:param kwargs: Passed to logging.Formatter
|
|
42
54
|
"""
|
|
43
|
-
super().__init__(fmt
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
super().__init__(fmt, *args, **kwargs)
|
|
56
|
+
self._dim_level: int = dim_level
|
|
57
|
+
self._cmap: dict[str, Color] = dict(colors)
|
|
58
|
+
self._lvl_cmap: dict[int, Color] = self.DEFAULT_LEVEL_COLORS | level_colors
|
|
47
59
|
self._widths = defaultdict(int, self.DEFAULT_CUTE_WIDTHS | cute_widths)
|
|
48
60
|
if any(not i.startswith("cute_") for i in self._widths):
|
|
49
61
|
raise ValueError("cute_widths must only contain cute_ parameters")
|
|
@@ -53,7 +65,7 @@ class CuteFormatter(Formatter):
|
|
|
53
65
|
"""
|
|
54
66
|
Set the colors for all loggers; enables colors if disabled
|
|
55
67
|
"""
|
|
56
|
-
self._cmap
|
|
68
|
+
self._cmap |= colors
|
|
57
69
|
|
|
58
70
|
def format(self, record: LogRecord) -> str:
|
|
59
71
|
levelname: str = record.levelname.ljust(self._widths["cute_levelname"])
|
|
@@ -62,26 +74,22 @@ class CuteFormatter(Formatter):
|
|
|
62
74
|
message: str = record.getMessage().ljust(self._widths["cute_message"])
|
|
63
75
|
if self.colored:
|
|
64
76
|
# Color level
|
|
65
|
-
if record.levelno
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
levelname =
|
|
71
|
-
elif record.levelno >= INFO:
|
|
72
|
-
levelname = Color.blue(levelname)
|
|
73
|
-
elif record.levelno < DEBUG:
|
|
74
|
-
levelname = Color.dim(levelname)
|
|
77
|
+
opts = tuple(i for i in self._lvl_cmap if i <= record.levelno)
|
|
78
|
+
col: Color | None = self._lvl_cmap[max(opts)] if opts else None
|
|
79
|
+
if record.levelno < self._dim_level:
|
|
80
|
+
col = Color.dim if col is None else (col + Color.dim)
|
|
81
|
+
if col is not None:
|
|
82
|
+
levelname = col(levelname)
|
|
75
83
|
# Color text
|
|
76
84
|
if (col := self._cmap.get(record.name, None)) is None:
|
|
77
85
|
c: int = adler32(record.name.encode()) % 7
|
|
78
86
|
col = Color(RawColor(PureColor.black.value + c)) if c != 0 else Color.default
|
|
79
|
-
if record.levelno <
|
|
87
|
+
if record.levelno < self._dim_level:
|
|
80
88
|
col += Color.dim
|
|
81
89
|
message = col(message)
|
|
82
90
|
name = col(name)
|
|
83
91
|
# Color timestamp
|
|
84
|
-
if record.levelno <
|
|
92
|
+
if record.levelno < self._dim_level:
|
|
85
93
|
asctime = Color.dim(asctime)
|
|
86
94
|
# Create an updated record then format that
|
|
87
95
|
new = copy(record)
|
|
@@ -3,13 +3,11 @@ import logging
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class _State:
|
|
6
|
+
__init__ = None # type: ignore
|
|
6
7
|
start: bool = False
|
|
7
8
|
ready: bool = False
|
|
8
9
|
lock = Lock()
|
|
9
10
|
|
|
10
|
-
def __init__(self):
|
|
11
|
-
raise NotImplementedError()
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
def install(*, value=logging.DEBUG // 2, force: bool = False) -> None:
|
|
15
13
|
"""
|
|
@@ -19,13 +19,13 @@ class SingletonType(type):
|
|
|
19
19
|
Intercept all class definitions to prevent subclassing singleton types except for Singleton
|
|
20
20
|
"""
|
|
21
21
|
if "__init_subclass__" in attrs:
|
|
22
|
-
raise
|
|
22
|
+
raise TypeError("Singleton's should not be subclassed or implement __init_subclass__")
|
|
23
23
|
|
|
24
24
|
def _init_subclass(cls):
|
|
25
25
|
with mcs._types_lock:
|
|
26
26
|
if any(issubclass(cls, i) for i in mcs._types if i is not Singleton):
|
|
27
27
|
err = "Do not derive from Singletons other than the base Singleton type"
|
|
28
|
-
raise
|
|
28
|
+
raise TypeError(err)
|
|
29
29
|
mcs._types.append(cls)
|
|
30
30
|
|
|
31
31
|
attrs["__init_subclass__"] = _init_subclass
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: zstdlib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: A set of useful python utilities
|
|
5
5
|
License: GPLv3
|
|
6
6
|
Project-URL: Homepage, https://github.com/zwimer/zstdlib
|
|
7
7
|
Keywords: remote,pipe
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
|
|
@@ -6,6 +6,7 @@ tests/not_set.py
|
|
|
6
6
|
tests/test_ansi.py
|
|
7
7
|
tests/test_enum.py
|
|
8
8
|
tests/test_frozen.py
|
|
9
|
+
tests/test_io.py
|
|
9
10
|
tests/test_singleton.py
|
|
10
11
|
tests/log/__init__.py
|
|
11
12
|
tests/log/base.py
|
|
@@ -15,6 +16,7 @@ zstdlib/__init__.py
|
|
|
15
16
|
zstdlib/ansi.py
|
|
16
17
|
zstdlib/enum.py
|
|
17
18
|
zstdlib/frozen.py
|
|
19
|
+
zstdlib/io.py
|
|
18
20
|
zstdlib/not_set.py
|
|
19
21
|
zstdlib/py.typed
|
|
20
22
|
zstdlib/singleton.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|