python-misc-utils 0.2__py3-none-any.whl
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.
- py_misc_utils/__init__.py +0 -0
- py_misc_utils/abs_timeout.py +12 -0
- py_misc_utils/alog.py +311 -0
- py_misc_utils/app_main.py +179 -0
- py_misc_utils/archive_streamer.py +112 -0
- py_misc_utils/assert_checks.py +118 -0
- py_misc_utils/ast_utils.py +121 -0
- py_misc_utils/async_manager.py +189 -0
- py_misc_utils/break_control.py +63 -0
- py_misc_utils/buffered_iterator.py +35 -0
- py_misc_utils/cached_file.py +507 -0
- py_misc_utils/call_limiter.py +26 -0
- py_misc_utils/call_result_selector.py +13 -0
- py_misc_utils/cleanups.py +85 -0
- py_misc_utils/cmd.py +97 -0
- py_misc_utils/compression.py +116 -0
- py_misc_utils/cond_waiter.py +13 -0
- py_misc_utils/context_base.py +18 -0
- py_misc_utils/context_managers.py +67 -0
- py_misc_utils/core_utils.py +577 -0
- py_misc_utils/daemon_process.py +252 -0
- py_misc_utils/data_cache.py +46 -0
- py_misc_utils/date_utils.py +90 -0
- py_misc_utils/debug.py +24 -0
- py_misc_utils/dyn_modules.py +50 -0
- py_misc_utils/dynamod.py +103 -0
- py_misc_utils/env_config.py +35 -0
- py_misc_utils/executor.py +239 -0
- py_misc_utils/file_overwrite.py +29 -0
- py_misc_utils/fin_wrap.py +77 -0
- py_misc_utils/fp_utils.py +47 -0
- py_misc_utils/fs/__init__.py +0 -0
- py_misc_utils/fs/file_fs.py +127 -0
- py_misc_utils/fs/ftp_fs.py +242 -0
- py_misc_utils/fs/gcs_fs.py +196 -0
- py_misc_utils/fs/http_fs.py +241 -0
- py_misc_utils/fs/s3_fs.py +417 -0
- py_misc_utils/fs_base.py +133 -0
- py_misc_utils/fs_utils.py +207 -0
- py_misc_utils/gcs_fs.py +169 -0
- py_misc_utils/gen_indices.py +54 -0
- py_misc_utils/gfs.py +371 -0
- py_misc_utils/git_repo.py +77 -0
- py_misc_utils/global_namespace.py +110 -0
- py_misc_utils/http_async_fetcher.py +139 -0
- py_misc_utils/http_server.py +196 -0
- py_misc_utils/http_utils.py +143 -0
- py_misc_utils/img_utils.py +20 -0
- py_misc_utils/infix_op.py +20 -0
- py_misc_utils/inspect_utils.py +205 -0
- py_misc_utils/iostream.py +21 -0
- py_misc_utils/iter_file.py +117 -0
- py_misc_utils/key_wrap.py +46 -0
- py_misc_utils/lazy_import.py +25 -0
- py_misc_utils/lockfile.py +164 -0
- py_misc_utils/mem_size.py +64 -0
- py_misc_utils/mirror_from.py +72 -0
- py_misc_utils/mmap.py +16 -0
- py_misc_utils/module_utils.py +196 -0
- py_misc_utils/moving_average.py +19 -0
- py_misc_utils/msgpack_streamer.py +26 -0
- py_misc_utils/multi_wait.py +24 -0
- py_misc_utils/multiprocessing.py +102 -0
- py_misc_utils/named_array.py +224 -0
- py_misc_utils/no_break.py +46 -0
- py_misc_utils/no_except.py +32 -0
- py_misc_utils/np_ml_framework.py +184 -0
- py_misc_utils/np_utils.py +346 -0
- py_misc_utils/ntuple_utils.py +38 -0
- py_misc_utils/num_utils.py +54 -0
- py_misc_utils/obj.py +73 -0
- py_misc_utils/object_cache.py +100 -0
- py_misc_utils/object_tracker.py +88 -0
- py_misc_utils/ordered_set.py +71 -0
- py_misc_utils/osfd.py +27 -0
- py_misc_utils/packet.py +22 -0
- py_misc_utils/parquet_streamer.py +69 -0
- py_misc_utils/pd_utils.py +254 -0
- py_misc_utils/periodic_task.py +61 -0
- py_misc_utils/pickle_wrap.py +121 -0
- py_misc_utils/pipeline.py +98 -0
- py_misc_utils/remap_pickle.py +50 -0
- py_misc_utils/resource_manager.py +155 -0
- py_misc_utils/rnd_utils.py +56 -0
- py_misc_utils/run_once.py +19 -0
- py_misc_utils/scheduler.py +135 -0
- py_misc_utils/select_params.py +300 -0
- py_misc_utils/signal.py +141 -0
- py_misc_utils/skl_utils.py +270 -0
- py_misc_utils/split.py +147 -0
- py_misc_utils/state.py +53 -0
- py_misc_utils/std_module.py +56 -0
- py_misc_utils/stream_dataframe.py +176 -0
- py_misc_utils/streamed_file.py +144 -0
- py_misc_utils/tempdir.py +79 -0
- py_misc_utils/template_replace.py +51 -0
- py_misc_utils/tensor_stream.py +269 -0
- py_misc_utils/thread_context.py +33 -0
- py_misc_utils/throttle.py +30 -0
- py_misc_utils/time_trigger.py +18 -0
- py_misc_utils/timegen.py +11 -0
- py_misc_utils/traceback.py +49 -0
- py_misc_utils/tracking_executor.py +91 -0
- py_misc_utils/transform_array.py +42 -0
- py_misc_utils/uncompress.py +35 -0
- py_misc_utils/url_fetcher.py +157 -0
- py_misc_utils/utils.py +538 -0
- py_misc_utils/varint.py +50 -0
- py_misc_utils/virt_array.py +52 -0
- py_misc_utils/weak_call.py +33 -0
- py_misc_utils/work_results.py +100 -0
- py_misc_utils/writeback_file.py +43 -0
- python_misc_utils-0.2.dist-info/METADATA +36 -0
- python_misc_utils-0.2.dist-info/RECORD +117 -0
- python_misc_utils-0.2.dist-info/WHEEL +5 -0
- python_misc_utils-0.2.dist-info/licenses/LICENSE +13 -0
- python_misc_utils-0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from . import alog
|
|
4
|
+
from . import assert_checks as tas
|
|
5
|
+
from . import core_utils as cu
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def diff_split(data, mask_fn, sort=True):
|
|
9
|
+
if sort:
|
|
10
|
+
indices = np.argsort(data)
|
|
11
|
+
sdata = np.take_along_axis(data, indices, axis=None)
|
|
12
|
+
else:
|
|
13
|
+
indices, sdata = None, data
|
|
14
|
+
|
|
15
|
+
diff = np.diff(sdata)
|
|
16
|
+
mask = mask_fn(diff)
|
|
17
|
+
msteps = np.flatnonzero(np.asarray(mask))
|
|
18
|
+
|
|
19
|
+
sindices = np.arange(len(data))
|
|
20
|
+
splits = np.split(sindices, msteps + 1)
|
|
21
|
+
|
|
22
|
+
# Caller should use data[result[i]] to fetch split data.
|
|
23
|
+
return [indices[s] for s in splits] if indices is not None else splits
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def group_splits(data, mask_fn):
|
|
27
|
+
diff = np.diff(data)
|
|
28
|
+
mask = mask_fn(diff)
|
|
29
|
+
|
|
30
|
+
return np.flatnonzero(np.asarray(mask))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def group_by_delta(data, mask_fn):
|
|
34
|
+
return np.split(data, group_splits(data, mask_fn) + 1)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fillna(data, copy=False, defval=0):
|
|
38
|
+
if copy:
|
|
39
|
+
data = data.copy()
|
|
40
|
+
fdata = data.flatten()
|
|
41
|
+
mask = np.where(np.isnan(fdata))[0]
|
|
42
|
+
for r in group_by_delta(mask, lambda x: x != 1):
|
|
43
|
+
if r.size == 0:
|
|
44
|
+
continue
|
|
45
|
+
vi = r[-1] + 1
|
|
46
|
+
if vi < len(fdata):
|
|
47
|
+
rv = fdata[vi]
|
|
48
|
+
else:
|
|
49
|
+
vi = r[0] - 1
|
|
50
|
+
if vi >= 0:
|
|
51
|
+
rv = fdata[vi]
|
|
52
|
+
else:
|
|
53
|
+
rv = defval
|
|
54
|
+
|
|
55
|
+
fdata[r] = rv
|
|
56
|
+
|
|
57
|
+
return fdata.reshape(data.shape)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def infer_np_dtype(dtypes):
|
|
61
|
+
dtype = None
|
|
62
|
+
for t in dtypes:
|
|
63
|
+
if dtype is None or t == np.float64:
|
|
64
|
+
dtype = t
|
|
65
|
+
elif t == np.float32:
|
|
66
|
+
if dtype.itemsize > t.itemsize:
|
|
67
|
+
dtype = np.float64
|
|
68
|
+
else:
|
|
69
|
+
dtype = t
|
|
70
|
+
elif dtype != np.float64:
|
|
71
|
+
if dtype == np.float32 and t.itemsize > dtype.itemsize:
|
|
72
|
+
dtype = np.float64
|
|
73
|
+
elif t.itemsize > dtype.itemsize:
|
|
74
|
+
dtype = t
|
|
75
|
+
|
|
76
|
+
return dtype if dtype is not None else np.float32
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def maybe_stack_slices(slices, axis=0):
|
|
80
|
+
if slices and isinstance(slices[0], np.ndarray):
|
|
81
|
+
return np.stack(slices, axis=axis)
|
|
82
|
+
|
|
83
|
+
return slices
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def to_numpy(data):
|
|
87
|
+
if isinstance(data, np.ndarray):
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
npfn = getattr(data, 'to_numpy', None)
|
|
91
|
+
if callable(npfn):
|
|
92
|
+
return npfn()
|
|
93
|
+
|
|
94
|
+
# This is PyTorch ...
|
|
95
|
+
npfn = getattr(data, 'numpy', None)
|
|
96
|
+
if callable(npfn):
|
|
97
|
+
return npfn(force=True)
|
|
98
|
+
|
|
99
|
+
return np.array(data)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_numeric(dtype):
|
|
103
|
+
return np.issubdtype(dtype, np.number)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_integer(dtype):
|
|
107
|
+
return np.issubdtype(dtype, np.integer)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def is_floating(dtype):
|
|
111
|
+
return np.issubdtype(dtype, np.floating)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_numpy(v):
|
|
115
|
+
return type(v).__module__ == np.__name__
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_sorted(data, descending=False):
|
|
119
|
+
if not isinstance(data, np.ndarray):
|
|
120
|
+
data = np.array(data)
|
|
121
|
+
|
|
122
|
+
if descending:
|
|
123
|
+
return np.all(data[:-1] >= data[1:])
|
|
124
|
+
|
|
125
|
+
return np.all(data[:-1] <= data[1:])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def astype(data, col, dtype):
|
|
129
|
+
if cu.isdict(dtype):
|
|
130
|
+
cdtype = dtype.get(col)
|
|
131
|
+
elif is_numeric(data.dtype):
|
|
132
|
+
cdtype = dtype
|
|
133
|
+
else:
|
|
134
|
+
cdtype = None
|
|
135
|
+
|
|
136
|
+
return data if cdtype is None else data.astype(cdtype)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def softmax(x):
|
|
140
|
+
e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
|
|
141
|
+
|
|
142
|
+
return e_x / np.sum(e_x, axis=-1, keepdims=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def categorical(un_probs, n=None):
|
|
146
|
+
probs = softmax(un_probs)
|
|
147
|
+
if probs.ndim == 1:
|
|
148
|
+
values = np.random.choice(len(probs), size=n or 1, p=probs)
|
|
149
|
+
|
|
150
|
+
return values[0] if n is None else values
|
|
151
|
+
else:
|
|
152
|
+
fprobs = np.reshape(probs, (-1, probs.shape[-1]))
|
|
153
|
+
values = []
|
|
154
|
+
for p in fprobs:
|
|
155
|
+
values.append(np.random.choice(len(p), size=n or 1, p=p))
|
|
156
|
+
|
|
157
|
+
catv = np.vstack(values)
|
|
158
|
+
|
|
159
|
+
return np.reshape(catv, tuple(probs.shape[: -1]) + (catv.shape[-1],))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def onehot(values, num_categories=None):
|
|
163
|
+
if num_categories is None:
|
|
164
|
+
num_categories = np.max(values) + 1
|
|
165
|
+
else:
|
|
166
|
+
tas.check_lt(np.max(values), num_categories)
|
|
167
|
+
|
|
168
|
+
return np.eye(num_categories)[values]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def normalize(data, axis=None):
|
|
172
|
+
mean = np.mean(data, axis=axis)
|
|
173
|
+
std = np.std(data, axis=axis)
|
|
174
|
+
|
|
175
|
+
if std.ndim > 0:
|
|
176
|
+
std[np.where(std == 0.0)] = 1.0
|
|
177
|
+
elif std == 0.0:
|
|
178
|
+
std = 1.0
|
|
179
|
+
|
|
180
|
+
return (data - mean) / std
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def moving_average(data, window, include_current=True):
|
|
184
|
+
weights = np.ones(window, dtype=data.dtype) / window
|
|
185
|
+
pdata = np.pad(data, (window, window), mode='edge')
|
|
186
|
+
cdata = np.convolve(pdata, weights, mode='valid')
|
|
187
|
+
base = 1 if include_current else 0
|
|
188
|
+
|
|
189
|
+
return cdata[base: base + len(data)]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def rolling_window(a, window):
|
|
193
|
+
shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
|
|
194
|
+
strides = a.strides + (a.strides[-1],)
|
|
195
|
+
|
|
196
|
+
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def shift(data, pos=1):
|
|
200
|
+
result = np.empty_like(data)
|
|
201
|
+
if pos > 0:
|
|
202
|
+
result[: pos] = data[0]
|
|
203
|
+
result[pos:] = data[: -pos]
|
|
204
|
+
elif pos < 0:
|
|
205
|
+
result[pos:] = data[-1]
|
|
206
|
+
result[: pos] = data[-pos:]
|
|
207
|
+
else:
|
|
208
|
+
result[:] = data
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def complement_indices(indices, size):
|
|
214
|
+
all_indices = np.full(size, 1, dtype=np.int8)
|
|
215
|
+
all_indices[indices] = 0
|
|
216
|
+
|
|
217
|
+
return np.flatnonzero(all_indices)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def polyfit_std(yv, xv=None, deg=1):
|
|
221
|
+
xv = np.arange(len(yv), dtype=np.float32) if xv is None else np.array(xv)
|
|
222
|
+
yv = yv if isinstance(yv, np.ndarray) else np.array(yv)
|
|
223
|
+
|
|
224
|
+
yfit = np.polynomial.Polynomial.fit(xv, yv, deg)
|
|
225
|
+
fyv = yfit(xv)
|
|
226
|
+
|
|
227
|
+
return np.std(yv - fyv), fyv, yfit
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def npdict_clone(npd):
|
|
231
|
+
cloned = type(npd)()
|
|
232
|
+
for k, v in npd.items():
|
|
233
|
+
cloned[k] = np.array(v)
|
|
234
|
+
|
|
235
|
+
return cloned
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def is_ordered(v, reverse=False):
|
|
239
|
+
npv = to_numpy(v)
|
|
240
|
+
|
|
241
|
+
return np.all(npv[:-1] >= npv[1:]) if reverse else np.all(npv[:-1] <= npv[1:])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class RingBuffer:
|
|
245
|
+
|
|
246
|
+
def __init__(self, capacity, dtype, vshape):
|
|
247
|
+
self.capacity = capacity
|
|
248
|
+
self.dtype = dtype
|
|
249
|
+
self._vshape = tuple(vshape)
|
|
250
|
+
self._count = 0
|
|
251
|
+
self._data = np.zeros((capacity,) + self._vshape, dtype=dtype)
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def shape(self):
|
|
255
|
+
return (len(self),) + self._vshape
|
|
256
|
+
|
|
257
|
+
def resize(self, capacity):
|
|
258
|
+
self._data = np.resize(self._data, (capacity,) + self._vshape)
|
|
259
|
+
self._count = min(self._count, self.capacity, capacity)
|
|
260
|
+
self.capacity = capacity
|
|
261
|
+
|
|
262
|
+
def select(self, indices):
|
|
263
|
+
indices = indices[indices < len(self)]
|
|
264
|
+
self._count = len(indices)
|
|
265
|
+
self._data[: self._count] = self._data[indices]
|
|
266
|
+
|
|
267
|
+
def append(self, v):
|
|
268
|
+
self._data[self._count % self.capacity] = v
|
|
269
|
+
self._count += 1
|
|
270
|
+
|
|
271
|
+
def extend(self, v):
|
|
272
|
+
arr = np.asarray(v, dtype=self.dtype)
|
|
273
|
+
if self._vshape:
|
|
274
|
+
arr = arr.reshape((-1,) + self._vshape)
|
|
275
|
+
|
|
276
|
+
pos = self._count % self.capacity
|
|
277
|
+
front = min(self.capacity - pos, len(arr))
|
|
278
|
+
|
|
279
|
+
self._data[pos: pos + front] = arr[: front]
|
|
280
|
+
|
|
281
|
+
back = min(pos, len(arr) - front)
|
|
282
|
+
if back > 0:
|
|
283
|
+
self._data[: back] = arr[front: front + back]
|
|
284
|
+
|
|
285
|
+
self._count += front + back
|
|
286
|
+
|
|
287
|
+
def to_numpy(self):
|
|
288
|
+
return np.concatenate(tuple(self.iter_views()))
|
|
289
|
+
|
|
290
|
+
def data(self, dtype=None):
|
|
291
|
+
return np.array(self._data[: len(self)], dtype=dtype)
|
|
292
|
+
|
|
293
|
+
def iter_views(self):
|
|
294
|
+
if self._count <= self.capacity:
|
|
295
|
+
yield self._data[: self._count]
|
|
296
|
+
else:
|
|
297
|
+
pos = self._count % self.capacity
|
|
298
|
+
|
|
299
|
+
yield self._data[pos:]
|
|
300
|
+
|
|
301
|
+
if pos > 0:
|
|
302
|
+
yield self._data[: pos]
|
|
303
|
+
|
|
304
|
+
def iter_indices(self):
|
|
305
|
+
if self._count <= self.capacity:
|
|
306
|
+
return np.arange(0, self._count)
|
|
307
|
+
|
|
308
|
+
return np.arange(self._count, self._count + self.capacity) % self.capacity
|
|
309
|
+
|
|
310
|
+
def __len__(self):
|
|
311
|
+
return min(self.capacity, self._count)
|
|
312
|
+
|
|
313
|
+
def __getitem__(self, i):
|
|
314
|
+
if isinstance(i, int):
|
|
315
|
+
idx = (max(self._count, self.capacity) + i) % self.capacity
|
|
316
|
+
else:
|
|
317
|
+
# Allow seamless slicing in case of non-integer indexing.
|
|
318
|
+
idx = i
|
|
319
|
+
|
|
320
|
+
return self._data[idx]
|
|
321
|
+
|
|
322
|
+
def __array__(self, dtype=None):
|
|
323
|
+
arr = self.to_numpy()
|
|
324
|
+
|
|
325
|
+
return arr if dtype is None else arr.astype(dtype)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
_NP_ARRAY_TYPECODES = {
|
|
329
|
+
bool: 'B',
|
|
330
|
+
int: 'q',
|
|
331
|
+
np.int8: 'b',
|
|
332
|
+
np.uint8: 'B',
|
|
333
|
+
np.int16: 'h',
|
|
334
|
+
np.uint16: 'H',
|
|
335
|
+
np.int32: 'l',
|
|
336
|
+
np.uint32: 'L',
|
|
337
|
+
np.int64: 'q',
|
|
338
|
+
np.uint64: 'Q',
|
|
339
|
+
np.float16: 'f',
|
|
340
|
+
np.float32: 'f',
|
|
341
|
+
np.float64: 'd',
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
def array_typecode(dtype):
|
|
345
|
+
return _NP_ARRAY_TYPECODES.get(dtype.type)
|
|
346
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from . import assert_checks as tas
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def extend(base_nt, name, fields, defaults=None):
|
|
8
|
+
if isinstance(fields, str):
|
|
9
|
+
fields = re.split(r'\s*[ ,]\s*', fields)
|
|
10
|
+
|
|
11
|
+
ext_fields, ext_def_fields, ext_defaults = [], [], []
|
|
12
|
+
missing = object()
|
|
13
|
+
for field in base_nt._fields:
|
|
14
|
+
defval = base_nt._field_defaults.get(field, missing)
|
|
15
|
+
if defval is missing:
|
|
16
|
+
ext_fields.append(field)
|
|
17
|
+
else:
|
|
18
|
+
ext_def_fields.append(field)
|
|
19
|
+
ext_defaults.append(defval)
|
|
20
|
+
|
|
21
|
+
defaults = defaults or ()
|
|
22
|
+
for i, field in enumerate(fields):
|
|
23
|
+
defidx = i - (len(fields) - len(defaults))
|
|
24
|
+
if defidx >= 0:
|
|
25
|
+
tas.check(field not in ext_def_fields,
|
|
26
|
+
msg=f'Field already exists: {field} in {ext_def_fields}')
|
|
27
|
+
tas.check(field not in ext_fields,
|
|
28
|
+
msg=f'Field already exists: {field} in {ext_fields}')
|
|
29
|
+
ext_def_fields.append(field)
|
|
30
|
+
ext_defaults.append(defaults[defidx])
|
|
31
|
+
else:
|
|
32
|
+
tas.check(field not in ext_fields,
|
|
33
|
+
msg=f'Field already exists: {field} in {ext_fields}')
|
|
34
|
+
ext_fields.append(field)
|
|
35
|
+
|
|
36
|
+
return collections.namedtuple(name, tuple(ext_fields + ext_def_fields),
|
|
37
|
+
defaults=tuple(ext_defaults))
|
|
38
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
def prime_factors(n):
|
|
3
|
+
i = 2
|
|
4
|
+
while i * i <= n:
|
|
5
|
+
q, r = divmod(n, i)
|
|
6
|
+
if r == 0:
|
|
7
|
+
n = q
|
|
8
|
+
yield i
|
|
9
|
+
else:
|
|
10
|
+
i += 1
|
|
11
|
+
|
|
12
|
+
if n > 1:
|
|
13
|
+
yield n
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def nearest_divisor(value, n):
|
|
17
|
+
nmin, nmax = n, n
|
|
18
|
+
|
|
19
|
+
while nmin > 1:
|
|
20
|
+
if value % nmin == 0:
|
|
21
|
+
break
|
|
22
|
+
nmin -= 1
|
|
23
|
+
|
|
24
|
+
dmin = n - nmin
|
|
25
|
+
top_n = min(n + dmin, value // 2)
|
|
26
|
+
|
|
27
|
+
while nmax <= top_n:
|
|
28
|
+
if value % nmax == 0:
|
|
29
|
+
break
|
|
30
|
+
nmax += 1
|
|
31
|
+
|
|
32
|
+
if value % nmax != 0:
|
|
33
|
+
return nmin
|
|
34
|
+
|
|
35
|
+
return nmin if dmin < (nmax - n) else nmax
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def sign_extend(value, nbits):
|
|
39
|
+
sign = 1 << (nbits - 1)
|
|
40
|
+
|
|
41
|
+
return (value & (sign - 1)) - (value & sign)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def round_up(v, step):
|
|
45
|
+
return ((v + step - 1) // step) * step
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def round_down(v, step):
|
|
49
|
+
return (v // step) * step
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def mix(a, b, gamma):
|
|
53
|
+
return a * gamma + b * (1.0 - gamma)
|
|
54
|
+
|
py_misc_utils/obj.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from . import core_utils as cu
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Obj:
|
|
7
|
+
|
|
8
|
+
def __init__(self, **kwargs):
|
|
9
|
+
self.update(**kwargs)
|
|
10
|
+
|
|
11
|
+
def update(self, **kwargs):
|
|
12
|
+
vars(self).update(kwargs)
|
|
13
|
+
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def update_from(self, obj):
|
|
17
|
+
vars(self).update(vars(obj))
|
|
18
|
+
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def clone(self, **kwargs):
|
|
22
|
+
nobj = copy.copy(self)
|
|
23
|
+
nobj.update(**kwargs)
|
|
24
|
+
|
|
25
|
+
return nobj
|
|
26
|
+
|
|
27
|
+
def as_dict(self):
|
|
28
|
+
ad = dict()
|
|
29
|
+
for k, v in vars(self).items():
|
|
30
|
+
if isinstance(v, Obj):
|
|
31
|
+
v = v.as_dict()
|
|
32
|
+
elif isinstance(v, (list, tuple)):
|
|
33
|
+
vals = []
|
|
34
|
+
for x in v:
|
|
35
|
+
if isinstance(x, Obj):
|
|
36
|
+
x = x.as_dict()
|
|
37
|
+
vals.append(x)
|
|
38
|
+
|
|
39
|
+
v = type(v)(vals)
|
|
40
|
+
elif cu.isdict(v):
|
|
41
|
+
vd = type(v)()
|
|
42
|
+
for z, x in v.items():
|
|
43
|
+
if isinstance(x, Obj):
|
|
44
|
+
x = x.as_dict()
|
|
45
|
+
vd[z] = x
|
|
46
|
+
|
|
47
|
+
v = vd
|
|
48
|
+
|
|
49
|
+
ad[k] = v
|
|
50
|
+
|
|
51
|
+
return ad
|
|
52
|
+
|
|
53
|
+
def __eq__(self, other):
|
|
54
|
+
missing = object()
|
|
55
|
+
for k, v in vars(self).items():
|
|
56
|
+
ov = getattr(other, k, missing)
|
|
57
|
+
if ov is missing or v != ov:
|
|
58
|
+
return False
|
|
59
|
+
for k in vars(other).keys():
|
|
60
|
+
if not hasattr(self, k):
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
def __repr__(self):
|
|
66
|
+
values = ', '.join(f'{k}={str_value(v)}' for k, v in vars(self).items())
|
|
67
|
+
|
|
68
|
+
return f'{type(self).__name__}({values})'
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def str_value(v):
|
|
72
|
+
return '"' + v.replace('"', '\\"') + '"' if isinstance(v, str) else str(v)
|
|
73
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import collections
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from . import alog
|
|
9
|
+
from . import fin_wrap as fw
|
|
10
|
+
from . import periodic_task as ptsk
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_Entry = collections.namedtuple('Entry', 'name, obj, handler, time')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Handler(abc.ABC):
|
|
17
|
+
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def create(self):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def max_age(self):
|
|
23
|
+
return 60
|
|
24
|
+
|
|
25
|
+
def is_alive(self, obj):
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
def close(self, obj):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Cache:
|
|
33
|
+
|
|
34
|
+
def __init__(self, clean_timeo=None):
|
|
35
|
+
self._lock = threading.Lock()
|
|
36
|
+
self._cond = threading.Condition(lock=self._lock)
|
|
37
|
+
self._cache = collections.defaultdict(collections.deque)
|
|
38
|
+
self._cleaner = ptsk.PeriodicTask(
|
|
39
|
+
'CacheCleaner',
|
|
40
|
+
self._try_cleanup,
|
|
41
|
+
clean_timeo or int(os.getenv('CACHE_CLEAN_TIMEO', 8)),
|
|
42
|
+
stop_on_error=False,
|
|
43
|
+
)
|
|
44
|
+
self._cleaner.start()
|
|
45
|
+
|
|
46
|
+
def _try_cleanup(self):
|
|
47
|
+
alog.verbose(f'Object cache cleanup running')
|
|
48
|
+
cleans = []
|
|
49
|
+
with self._lock:
|
|
50
|
+
new_cache = collections.defaultdict(collections.deque)
|
|
51
|
+
for name, cqueue in self._cache.items():
|
|
52
|
+
for entry in cqueue:
|
|
53
|
+
age = time.time() - entry.time
|
|
54
|
+
if age > entry.handler.max_age():
|
|
55
|
+
cleans.append(entry)
|
|
56
|
+
else:
|
|
57
|
+
new_cache[name].append(entry)
|
|
58
|
+
|
|
59
|
+
self._cache = new_cache
|
|
60
|
+
|
|
61
|
+
for entry in cleans:
|
|
62
|
+
alog.debug(f'Cache Clean: name={entry.name} obj={entry.obj}')
|
|
63
|
+
entry.handler.close(entry.obj)
|
|
64
|
+
|
|
65
|
+
def shutdown(self):
|
|
66
|
+
self._cleaner.stop()
|
|
67
|
+
|
|
68
|
+
def _release(self, name, handler, obj):
|
|
69
|
+
alog.debug(f'Cache Release: name={name} obj={obj}')
|
|
70
|
+
with self._lock:
|
|
71
|
+
self._cache[name].append(_Entry(name=name,
|
|
72
|
+
obj=obj,
|
|
73
|
+
handler=handler,
|
|
74
|
+
time=time.time()))
|
|
75
|
+
|
|
76
|
+
def get(self, name, handler):
|
|
77
|
+
with self._lock:
|
|
78
|
+
cqueue, obj = self._cache[name], None
|
|
79
|
+
if cqueue:
|
|
80
|
+
entry = cqueue.popleft()
|
|
81
|
+
if not entry.handler.is_alive(entry.obj):
|
|
82
|
+
entry.handler.close(obj)
|
|
83
|
+
obj = None
|
|
84
|
+
else:
|
|
85
|
+
obj = entry.obj
|
|
86
|
+
alog.debug(f'Cache Hit: name={name} obj={obj}')
|
|
87
|
+
|
|
88
|
+
if obj is None:
|
|
89
|
+
obj = handler.create()
|
|
90
|
+
|
|
91
|
+
finfn = functools.partial(self._release, name, handler, obj)
|
|
92
|
+
|
|
93
|
+
return fw.FinWrapper(obj, finfn)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
_CACHE = Cache()
|
|
97
|
+
|
|
98
|
+
def cache():
|
|
99
|
+
return _CACHE
|
|
100
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import gc
|
|
3
|
+
|
|
4
|
+
from . import alog
|
|
5
|
+
from . import core_utils as cu
|
|
6
|
+
from . import inspect_utils as iu
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_referred(obj):
|
|
10
|
+
referred = []
|
|
11
|
+
if cu.isdict(obj):
|
|
12
|
+
for name, value in obj.items():
|
|
13
|
+
referred.append((name, value))
|
|
14
|
+
elif isinstance(obj, (list, tuple)):
|
|
15
|
+
for i, value in enumerate(obj):
|
|
16
|
+
referred.append((f'[{i}]', value))
|
|
17
|
+
elif hasattr(obj, '__dict__'):
|
|
18
|
+
for name, value in vars(obj).items():
|
|
19
|
+
referred.append((name, value))
|
|
20
|
+
|
|
21
|
+
return tuple(referred)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_tracking_references(obj, tracked_by, max_references=None):
|
|
25
|
+
max_references = max_references or 8
|
|
26
|
+
|
|
27
|
+
references = []
|
|
28
|
+
to_track = [(obj, None)]
|
|
29
|
+
while to_track:
|
|
30
|
+
tobj, tname = to_track.pop()
|
|
31
|
+
ntrack = 0
|
|
32
|
+
for rname, robj in tracked_by.get(id(tobj), ()):
|
|
33
|
+
suffix = ''
|
|
34
|
+
if tname is not None:
|
|
35
|
+
sep = '' if tname.startswith('[') else '.'
|
|
36
|
+
suffix = f'{sep}{tname}'
|
|
37
|
+
|
|
38
|
+
to_track.append((robj, f'{rname}{suffix}'))
|
|
39
|
+
ntrack += 1
|
|
40
|
+
|
|
41
|
+
if ntrack == 0:
|
|
42
|
+
references.append((iu.qual_name(tobj), tname))
|
|
43
|
+
if len(references) >= max_references:
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
return tuple(references)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def track_objects(tracker, max_references=None):
|
|
50
|
+
gc.collect()
|
|
51
|
+
|
|
52
|
+
gc_objs = gc.get_objects()
|
|
53
|
+
|
|
54
|
+
all_objs = dict()
|
|
55
|
+
tracking = collections.defaultdict(list)
|
|
56
|
+
tracked_by = collections.defaultdict(list)
|
|
57
|
+
for obj in gc_objs:
|
|
58
|
+
all_objs[id(obj)] = obj
|
|
59
|
+
|
|
60
|
+
referred = _get_referred(obj)
|
|
61
|
+
for rname, robj in referred:
|
|
62
|
+
tracking[id(obj)].append((rname, robj))
|
|
63
|
+
tracked_by[id(robj)].append((rname, obj))
|
|
64
|
+
all_objs[id(robj)] = robj
|
|
65
|
+
|
|
66
|
+
report = []
|
|
67
|
+
for obj in all_objs.values():
|
|
68
|
+
try:
|
|
69
|
+
if (trackres := tracker.track(obj)) is not None:
|
|
70
|
+
prio, info = trackres
|
|
71
|
+
|
|
72
|
+
refs = _get_tracking_references(obj, tracked_by,
|
|
73
|
+
max_references=max_references)
|
|
74
|
+
|
|
75
|
+
treport = [info]
|
|
76
|
+
for r in refs:
|
|
77
|
+
treport.append(f' refby = {r[0]} ({r[1]})')
|
|
78
|
+
|
|
79
|
+
report.append((prio, treport))
|
|
80
|
+
except Exception as ex:
|
|
81
|
+
alog.warning(f'Exception while tracking objects: {ex}')
|
|
82
|
+
|
|
83
|
+
sreport = []
|
|
84
|
+
for r in sorted(report, key=lambda r: r[0]):
|
|
85
|
+
sreport.extend(r[1])
|
|
86
|
+
|
|
87
|
+
return '\n'.join(sreport)
|
|
88
|
+
|