pyutilkit 0.1.0__tar.gz → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyutilkit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: python's missing batteries
5
5
  Home-page: https://pyutilkit.readthedocs.io/en/stable/
6
6
  License: LGPL-3.0+
@@ -126,7 +126,7 @@ skip_empty = true
126
126
 
127
127
  [tool.poetry]
128
128
  name = "pyutilkit"
129
- version = "0.1.0"
129
+ version = "0.2.0"
130
130
  description = "python's missing batteries"
131
131
  authors = [
132
132
  "Stephanos Kuma <stephanos@kuma.ai>",
@@ -161,6 +161,7 @@ pipdeptree = "^2.13"
161
161
  black = "^24.1"
162
162
  mypy = "^1.8"
163
163
  ruff = "^0.4"
164
+ typing_extensions = "^4.11"
164
165
 
165
166
  [tool.poetry.group.test.dependencies]
166
167
  freezegun = "^1.5"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import logging
5
+ from collections.abc import Callable
6
+ from functools import wraps
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, TypeVar
9
+
10
+ if TYPE_CHECKING:
11
+ from typing_extensions import ParamSpec # py3.9: import from typing
12
+
13
+ P = ParamSpec("P")
14
+
15
+ logger = logging.getLogger(__name__)
16
+ INGEST_ERROR = "Function `%s` threw `%s` when called with args=%s and kwargs=%s"
17
+ R_co = TypeVar("R_co", covariant=True)
18
+
19
+
20
+ def handle_exceptions(
21
+ *,
22
+ exceptions: tuple[type[Exception], ...] = (Exception,),
23
+ default: R_co | None = None,
24
+ log_level: str = "info",
25
+ ) -> Callable[[Callable[P, R_co]], Callable[P, R_co | None]]:
26
+ def decorator(func: Callable[P, R_co]) -> Callable[P, R_co | None]:
27
+ @wraps(func)
28
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R_co | None:
29
+ try:
30
+ return func(*args, **kwargs)
31
+ except exceptions as exc:
32
+ getattr(logger, log_level)(
33
+ INGEST_ERROR,
34
+ func.__name__,
35
+ exc.__class__.__name__,
36
+ args,
37
+ kwargs,
38
+ exc_info=True,
39
+ )
40
+ return default
41
+
42
+ return wrapper
43
+
44
+ return decorator
45
+
46
+
47
+ def hash_file(path: Path, buffer_size: int = 2**16) -> str:
48
+ sha256 = hashlib.sha256()
49
+
50
+ with path.open("rb") as f:
51
+ while data := f.read(buffer_size):
52
+ sha256.update(data)
53
+
54
+ return sha256.hexdigest()
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Iterable
5
+ from enum import IntEnum, unique
6
+ from math import ceil, floor
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ if TYPE_CHECKING:
10
+ from typing_extensions import Self # py3.10: import from typing
11
+
12
+
13
+ @unique
14
+ class SGRCodes(IntEnum):
15
+ RESET = 0
16
+
17
+ BOLD = 1
18
+ ITALIC = 3
19
+ UNDERLINE = 4
20
+ BLINK = 5
21
+ REVERSE = 7
22
+ CONCEAL = 8
23
+
24
+ BLACK = 30
25
+ RED = 31
26
+ GREEN = 32
27
+ YELLOW = 33
28
+ BLUE = 34
29
+ MAGENTA = 35
30
+ CYAN = 36
31
+ GREY = 37
32
+
33
+ BG_BLACK = 40
34
+ BG_RED = 41
35
+ BG_GREEN = 42
36
+ BG_YELLOW = 43
37
+ BG_BLUE = 44
38
+ BG_MAGENTA = 45
39
+ BG_CYAN = 46
40
+ BG_GREY = 47
41
+
42
+ BLACK_BRIGHT = 90
43
+ RED_BRIGHT = 91
44
+ GREEN_BRIGHT = 92
45
+ YELLOW_BRIGHT = 93
46
+ BLUE_BRIGHT = 94
47
+ MAGENTA_BRIGHT = 95
48
+ CYAN_BRIGHT = 96
49
+ WHITE_BRIGHT = 97
50
+
51
+ BG_BLACK_BRIGHT = 100
52
+ BG_RED_BRIGHT = 101
53
+ BG_GREEN_BRIGHT = 102
54
+ BG_YELLOW_BRIGHT = 103
55
+ BG_BLUE_BRIGHT = 104
56
+ BG_MAGENTA_BRIGHT = 105
57
+ BG_CYAN_BRIGHT = 106
58
+ BG_WHITE_BRIGHT = 107
59
+
60
+ @property
61
+ def sequence(self) -> str:
62
+ return f"\033[{self.value}m"
63
+
64
+
65
+ class SGRString(str):
66
+ _sgr: tuple[SGRCodes, ...]
67
+ _string: str
68
+ __slots__ = ("_sgr", "_string")
69
+
70
+ def __new__(cls, obj: Any, *, params: Iterable[SGRCodes] = ()) -> Self:
71
+ string = super().__new__(cls, obj)
72
+ object.__setattr__(string, "_string", str(obj))
73
+ object.__setattr__(string, "_sgr", tuple(params))
74
+ return string
75
+
76
+ def __setattr__(self, name: str, value: Any) -> None:
77
+ msg = "SGRString is immutable"
78
+ raise AttributeError(msg)
79
+
80
+ def __delattr__(self, name: str) -> None:
81
+ msg = "SGRString is immutable"
82
+ raise AttributeError(msg)
83
+
84
+ def __str__(self) -> str:
85
+ if not self._sgr:
86
+ return self._string
87
+ prefix = "".join(code.sequence for code in self._sgr)
88
+ return f"{prefix}{self._string}{SGRCodes.RESET.sequence}"
89
+
90
+ def __mul__(self, other: Any) -> Self:
91
+ if not isinstance(other, int):
92
+ return NotImplemented
93
+ return type(self)(self._string * other, params=self._sgr)
94
+
95
+ def __rmul__(self, other: Any) -> Self:
96
+ if not isinstance(other, int):
97
+ return NotImplemented
98
+ return type(self)(self._string * other, params=self._sgr)
99
+
100
+ def header(
101
+ self,
102
+ *,
103
+ padding: str = " ",
104
+ left_spaces: int = 1,
105
+ right_spaces: int = 1,
106
+ space: str = " ",
107
+ ) -> None:
108
+ columns = os.get_terminal_size().columns
109
+ text = f"{space * left_spaces}{self}{space * right_spaces}"
110
+ title_length = left_spaces + len(self) + right_spaces
111
+ if title_length >= columns:
112
+ print(text.strip())
113
+ return
114
+
115
+ half = (columns - title_length) / 2
116
+ print(f"{padding * ceil(half)}{text}{padding * floor(half)}")
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"
@@ -1,96 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from collections.abc import Iterable
5
- from enum import IntEnum, unique
6
- from math import ceil, floor
7
- from typing import Any
8
-
9
-
10
- @unique
11
- class SGRCodes(IntEnum):
12
- RESET = 0
13
-
14
- BOLD = 1
15
- ITALIC = 3
16
- UNDERLINE = 4
17
- BLINK = 5
18
- REVERSE = 7
19
- CONCEAL = 8
20
-
21
- BLACK = 30
22
- RED = 31
23
- GREEN = 32
24
- YELLOW = 33
25
- BLUE = 34
26
- MAGENTA = 35
27
- CYAN = 36
28
- GREY = 37
29
-
30
- BG_BLACK = 40
31
- BG_RED = 41
32
- BG_GREEN = 42
33
- BG_YELLOW = 43
34
- BG_BLUE = 44
35
- BG_MAGENTA = 45
36
- BG_CYAN = 46
37
- BG_GREY = 47
38
-
39
- BLACK_BRIGHT = 90
40
- RED_BRIGHT = 91
41
- GREEN_BRIGHT = 92
42
- YELLOW_BRIGHT = 93
43
- BLUE_BRIGHT = 94
44
- MAGENTA_BRIGHT = 95
45
- CYAN_BRIGHT = 96
46
- WHITE_BRIGHT = 97
47
-
48
- BG_BLACK_BRIGHT = 100
49
- BG_RED_BRIGHT = 101
50
- BG_GREEN_BRIGHT = 102
51
- BG_YELLOW_BRIGHT = 103
52
- BG_BLUE_BRIGHT = 104
53
- BG_MAGENTA_BRIGHT = 105
54
- BG_CYAN_BRIGHT = 106
55
- BG_WHITE_BRIGHT = 107
56
-
57
- @property
58
- def sequence(self) -> str:
59
- return f"\033[{self.value}m"
60
-
61
-
62
- class SGRString(str):
63
- _sgr: str
64
- __slots__ = ("_sgr",)
65
-
66
- def __new__(cls, value: Any, *, params: Iterable[SGRCodes] = ()) -> SGRString:
67
- string = super().__new__(cls, value)
68
- suffix = SGRCodes.RESET.sequence
69
- prefix = "".join(param.sequence for param in params) or SGRCodes.RESET.sequence
70
- object.__setattr__(string, "_sgr", f"{prefix}{value}{suffix}")
71
- return string
72
-
73
- def __setattr__(self, name: str, value: Any) -> None:
74
- msg = "SGRString is immutable"
75
- raise AttributeError(msg)
76
-
77
- def __delattr__(self, name: str) -> None:
78
- msg = "SGRString is immutable"
79
- raise AttributeError(msg)
80
-
81
- def __str__(self) -> str:
82
- return self._sgr
83
-
84
-
85
- def header(
86
- text: str, *, padding: str = " ", left_spaces: int = 1, right_spaces: int = 1
87
- ) -> None:
88
- columns = os.get_terminal_size().columns
89
- text = f"{' ' * left_spaces}{text.strip()}{' ' * right_spaces}"
90
- title_length = len(text)
91
- if title_length >= columns:
92
- print(text.strip())
93
- return
94
-
95
- half = (columns - len(text)) / 2
96
- print(f"{padding * ceil(half)}{text}{padding * floor(half)}")
File without changes
File without changes