easy-kit 0.0.3__tar.gz → 0.0.4.dev0__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.
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/PKG-INFO +1 -1
- easy_kit-0.0.4.dev0/easy_kit/__init__.py +0 -0
- easy_kit-0.0.4.dev0/easy_kit/event.py +39 -0
- easy_kit-0.0.4.dev0/easy_kit/timing.py +177 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/easy_kit.egg-info/PKG-INFO +1 -1
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/easy_kit.egg-info/SOURCES.txt +3 -0
- easy_kit-0.0.4.dev0/easy_kit.egg-info/top_level.txt +1 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/pyproject.toml +1 -1
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/setup.py +2 -4
- easy_kit-0.0.3/easy_kit.egg-info/top_level.txt +0 -1
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/LICENSE +0 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/README.md +0 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/easy_kit.egg-info/dependency_links.txt +0 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/setup.cfg +0 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/tests/test_event.py +0 -0
- {easy_kit-0.0.3 → easy_kit-0.0.4.dev0}/tests/test_timing.py +0 -0
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Event:
|
|
5
|
+
IS_ENABLED = False
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def enable(status: bool = True):
|
|
9
|
+
Event.IS_ENABLED = status
|
|
10
|
+
|
|
11
|
+
def __init__(self, name: str = 'EventDefault'):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.observer: list[Callable[[...], ...]] = []
|
|
14
|
+
self.enabled = True
|
|
15
|
+
self.verbose = False
|
|
16
|
+
|
|
17
|
+
def emitter(self, *args, **kwargs):
|
|
18
|
+
def _fun():
|
|
19
|
+
self.emit(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
return _fun
|
|
22
|
+
|
|
23
|
+
def emit(self, *args, **kwargs):
|
|
24
|
+
if self.enabled:
|
|
25
|
+
for _ in self.observer:
|
|
26
|
+
_(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
def connect[** P, T](self, func: Callable[P, T]) -> Callable[P, T]:
|
|
29
|
+
if Event.IS_ENABLED:
|
|
30
|
+
self.observer.append(func)
|
|
31
|
+
return func
|
|
32
|
+
|
|
33
|
+
def observe[** P, T](self, func: Callable[P, T]) -> Callable[P, T]:
|
|
34
|
+
def _fun(*args, **kwargs) -> T:
|
|
35
|
+
res = func(*args, **kwargs)
|
|
36
|
+
self.emit(res)
|
|
37
|
+
return res
|
|
38
|
+
|
|
39
|
+
return _fun
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import statistics
|
|
3
|
+
import time
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from pprint import pprint
|
|
9
|
+
from typing import Callable
|
|
10
|
+
from unittest import TestCase
|
|
11
|
+
|
|
12
|
+
HEADERS = ['label', 'total (s)', 'count', 'min', 'max', 'mean', 'std']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tabulate(headers: list[str], data: list[list[str]]):
|
|
16
|
+
try:
|
|
17
|
+
from tabulate import tabulate
|
|
18
|
+
return tabulate(
|
|
19
|
+
headers=headers,
|
|
20
|
+
floatfmt='.5f',
|
|
21
|
+
tabular_data=data,
|
|
22
|
+
)
|
|
23
|
+
except:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
raw = [
|
|
27
|
+
headers,
|
|
28
|
+
*[
|
|
29
|
+
[f'{_:5}' for _ in row]
|
|
30
|
+
for row in data
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
lengths = [
|
|
35
|
+
max(len(raw[row][col]) for row in range(len(raw)))
|
|
36
|
+
for col in range(len(headers))
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
raw.insert(1, ['-' * _ for _ in lengths])
|
|
40
|
+
|
|
41
|
+
return '\n'.join([
|
|
42
|
+
' '.join([_.ljust(l) for _, l in zip(row, lengths)])
|
|
43
|
+
for row in raw
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class TimeEntry:
|
|
49
|
+
events: list[float] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
def raw_line(self, key: str):
|
|
52
|
+
return [key, self.total, self.count, self.min, self.max, self.mean, self.std]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def total(self):
|
|
56
|
+
return sum(self.events)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def count(self):
|
|
60
|
+
return len(self.events)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def min(self):
|
|
64
|
+
return self._undefined(min(self.events))
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def max(self):
|
|
68
|
+
return self._undefined(max(self.events))
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def mean(self):
|
|
72
|
+
return self._undefined(statistics.mean(self.events))
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def std(self):
|
|
76
|
+
return self._undefined(statistics.pstdev(self.events))
|
|
77
|
+
|
|
78
|
+
def _undefined(self, value: float):
|
|
79
|
+
if len(self.events) <= 1:
|
|
80
|
+
return ''
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DefaultLogger:
|
|
85
|
+
debug = print
|
|
86
|
+
info = print
|
|
87
|
+
warning = print
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Timings:
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self.db: dict[str, TimeEntry] = defaultdict(lambda: TimeEntry())
|
|
93
|
+
self.active = False
|
|
94
|
+
self.logs = False
|
|
95
|
+
self.logger = DefaultLogger
|
|
96
|
+
|
|
97
|
+
@contextmanager
|
|
98
|
+
def timing(self, name: str = None):
|
|
99
|
+
if name is None:
|
|
100
|
+
name = inspect.stack()[11].function
|
|
101
|
+
start = self._before(name)
|
|
102
|
+
yield
|
|
103
|
+
self._after(name, start)
|
|
104
|
+
|
|
105
|
+
def time_func[** P, R](self, func: Callable[P, R]) -> Callable[P, R]:
|
|
106
|
+
@wraps(func)
|
|
107
|
+
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
108
|
+
with self.timing(func.__qualname__):
|
|
109
|
+
return func(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
return inner
|
|
112
|
+
|
|
113
|
+
def show_timing(self):
|
|
114
|
+
if not self.active:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
self.logger.info('\n' + self.format_table())
|
|
119
|
+
except Exception as e:
|
|
120
|
+
self.logger.warning(f'Warning: {e}')
|
|
121
|
+
|
|
122
|
+
def raw_table(self):
|
|
123
|
+
return sorted([
|
|
124
|
+
entry.raw_line(key)
|
|
125
|
+
for key, entry in self.db.items()
|
|
126
|
+
], key=lambda row: row[1], reverse=True)
|
|
127
|
+
|
|
128
|
+
def format_table(self):
|
|
129
|
+
return _tabulate(headers=HEADERS, data=self.raw_table())
|
|
130
|
+
|
|
131
|
+
def setup_timing(self, status: bool = True, logs: bool = False):
|
|
132
|
+
self.active = status
|
|
133
|
+
self.logs = logs
|
|
134
|
+
|
|
135
|
+
def tree_structure(self):
|
|
136
|
+
groups = {}
|
|
137
|
+
|
|
138
|
+
for key, entry in self.db.items():
|
|
139
|
+
try:
|
|
140
|
+
major, minor = key.split('.', maxsplit=1)
|
|
141
|
+
except:
|
|
142
|
+
major = '___'
|
|
143
|
+
minor = key
|
|
144
|
+
if major not in groups:
|
|
145
|
+
groups[major] = {}
|
|
146
|
+
groups[major][minor] = entry
|
|
147
|
+
return groups
|
|
148
|
+
|
|
149
|
+
def _before(self, name: str):
|
|
150
|
+
if self.logs:
|
|
151
|
+
self.logger.debug(f'+ {name}')
|
|
152
|
+
if self.active:
|
|
153
|
+
return time.time()
|
|
154
|
+
|
|
155
|
+
def _after(self, name: str, start: float | None):
|
|
156
|
+
if self.logs:
|
|
157
|
+
self.logger.debug(f'- {name}')
|
|
158
|
+
if start is not None:
|
|
159
|
+
total = time.time() - start
|
|
160
|
+
self.db[name].events.append(total)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_TIMING = Timings()
|
|
164
|
+
timing = _TIMING.timing
|
|
165
|
+
time_func = _TIMING.time_func
|
|
166
|
+
show_timing = _TIMING.show_timing
|
|
167
|
+
setup_timing = _TIMING.setup_timing
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TimingTestCase(TestCase):
|
|
171
|
+
@classmethod
|
|
172
|
+
def setUpClass(cls):
|
|
173
|
+
setup_timing()
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def tearDownClass(cls):
|
|
177
|
+
show_timing()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
easy_kit
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from setuptools import setup, find_packages
|
|
1
|
+
from setuptools import setup
|
|
4
2
|
|
|
5
3
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
6
4
|
long_description = fh.read()
|
|
@@ -17,7 +15,7 @@ setup(
|
|
|
17
15
|
"License :: OSI Approved :: MIT License",
|
|
18
16
|
"Operating System :: OS Independent",
|
|
19
17
|
],
|
|
20
|
-
packages=
|
|
18
|
+
packages=['easy_kit'],
|
|
21
19
|
# install_requires=[line for line in open('requirements.txt')],
|
|
22
20
|
python_requires=">=3.7",
|
|
23
21
|
include_package_data=True
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|