omlish 0.0.0.dev294__py3-none-any.whl → 0.0.0.dev296__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.
omlish/math/fixed.py ADDED
@@ -0,0 +1,390 @@
1
+ import enum
2
+ import functools
3
+ import typing as ta
4
+
5
+ from .. import check
6
+ from .. import lang
7
+
8
+
9
+ FixedWidthIntT = ta.TypeVar('FixedWidthIntT', bound='FixedWidthInt')
10
+
11
+
12
+ ##
13
+
14
+
15
+ class CheckedFixedWidthIntError(ValueError):
16
+ pass
17
+
18
+
19
+ class OverflowFixedWidthIntError(CheckedFixedWidthIntError):
20
+ pass
21
+
22
+
23
+ class UnderflowFixedWidthIntError(CheckedFixedWidthIntError):
24
+ pass
25
+
26
+
27
+ ##
28
+
29
+
30
+ def _get_exclusive_base_cls(cls: type, base_classes: ta.Iterable[type]) -> type:
31
+ return check.single(bcls for bcls in base_classes if issubclass(cls, bcls))
32
+
33
+
34
+ def _gen_scalar_proxy_method(name):
35
+ def inner(self, *args, **kwargs):
36
+ return self.__class__(orig(self, *args, **kwargs))
37
+
38
+ orig = getattr(int, name)
39
+ return functools.wraps(orig)(inner)
40
+
41
+
42
+ def _gen_tuple_proxy_method(name):
43
+ def inner(self, *args, **kwargs):
44
+ return tuple(map(self.__class__, orig(self, *args, **kwargs)))
45
+
46
+ orig = getattr(int, name)
47
+ return functools.wraps(orig)(inner)
48
+
49
+
50
+ class FixedWidthInt(int, lang.Abstract):
51
+ BITS: ta.ClassVar[int]
52
+
53
+ #
54
+
55
+ class Mode(enum.StrEnum):
56
+ CHECK = enum.auto()
57
+ CLAMP = enum.auto()
58
+ WRAP = enum.auto()
59
+
60
+ MODE: ta.ClassVar[Mode]
61
+
62
+ SIGNED: ta.ClassVar[bool]
63
+
64
+ #
65
+
66
+ MIN: ta.ClassVar[int]
67
+ MAX: ta.ClassVar[int]
68
+
69
+ MASK: ta.ClassVar[int]
70
+
71
+ #
72
+
73
+ def __init_subclass__(cls) -> None:
74
+ super().__init_subclass__()
75
+
76
+ if lang.is_abstract_class(cls):
77
+ return
78
+
79
+ #
80
+
81
+ bits = check.single({
82
+ check.isinstance(bbits, int)
83
+ for bcls in cls.__mro__
84
+ if (bbits := bcls.__dict__.get('BITS')) is not None
85
+ })
86
+
87
+ #
88
+
89
+ mode_base = _get_exclusive_base_cls(cls, _MODE_BASE_CLASSES)
90
+
91
+ if mode_base is CheckedInt:
92
+ cls.MODE = cls.Mode.CHECK
93
+
94
+ elif mode_base is ClampedInt:
95
+ cls.MODE = cls.Mode.CLAMP
96
+
97
+ elif mode_base is WrappedInt:
98
+ cls.MODE = cls.Mode.WRAP
99
+
100
+ else:
101
+ raise TypeError(cls)
102
+
103
+ #
104
+
105
+ sign_base = _get_exclusive_base_cls(cls, _SIGN_BASE_CLASSES)
106
+
107
+ if sign_base is SignedInt:
108
+ cls.SIGNED = True
109
+ cls.MIN = -(1 << (bits - 1))
110
+ cls.MAX = (1 << (bits - 1)) - 1
111
+
112
+ elif sign_base is UnsignedInt:
113
+ cls.SIGNED = False
114
+ cls.MIN = 0
115
+ cls.MAX = (1 << bits) - 1
116
+
117
+ else:
118
+ raise TypeError(cls)
119
+
120
+ #
121
+
122
+ cls.MASK = (1 << bits) - 1
123
+
124
+ #
125
+
126
+ @classmethod
127
+ def clamp(cls, value: int) -> int:
128
+ return max(min(value, cls.MAX), cls.MIN)
129
+
130
+ @classmethod
131
+ def check(cls, value: int) -> int:
132
+ if value > cls.MAX:
133
+ raise OverflowFixedWidthIntError(value)
134
+ elif value < cls.MIN:
135
+ raise UnderflowFixedWidthIntError(value)
136
+ return value
137
+
138
+ @classmethod
139
+ def wrap(cls, value: int) -> int:
140
+ return ((value - cls.MIN) & cls.MASK) + cls.MIN
141
+
142
+ #
143
+
144
+ _SCALAR_PROXY_METHODS = frozenset([
145
+ '__abs__',
146
+ '__add__',
147
+ '__and__',
148
+ '__floordiv__',
149
+ '__invert__',
150
+ '__lshift__',
151
+ '__mod__',
152
+ '__mul__',
153
+ '__neg__',
154
+ '__or__',
155
+ '__pos__',
156
+ '__pow__',
157
+ '__radd__',
158
+ '__rand__',
159
+ '__rfloordiv__',
160
+ '__rlshift__',
161
+ '__rmod__',
162
+ '__rmul__',
163
+ '__ror__',
164
+ '__rpow__',
165
+ '__rrshift__',
166
+ '__rshift__',
167
+ '__rsub__',
168
+ '__rtruediv__',
169
+ '__rxor__',
170
+ '__sub__',
171
+ '__truediv__',
172
+ '__xor__',
173
+ ])
174
+
175
+ _TUPLE_PROXY_METHODS = frozenset([
176
+ '__divmod__',
177
+ '__rdivmod__',
178
+ ])
179
+
180
+ for _proxy_name in _SCALAR_PROXY_METHODS:
181
+ locals()[_proxy_name] = _gen_scalar_proxy_method(_proxy_name)
182
+ for _proxy_name in _TUPLE_PROXY_METHODS:
183
+ locals()[_proxy_name] = _gen_tuple_proxy_method(_proxy_name)
184
+ del _proxy_name
185
+
186
+ def __repr__(self) -> str:
187
+ return f'{self.__class__.__name__}({int(self)})'
188
+
189
+
190
+ ##
191
+
192
+
193
+ class SignedInt(FixedWidthInt, lang.Abstract):
194
+ pass
195
+
196
+
197
+ class UnsignedInt(FixedWidthInt, lang.Abstract):
198
+ pass
199
+
200
+
201
+ _SIGN_BASE_CLASSES: tuple[type[FixedWidthInt], ...] = (
202
+ SignedInt,
203
+ UnsignedInt,
204
+ )
205
+
206
+
207
+ #
208
+
209
+
210
+ class CheckedInt(FixedWidthInt, lang.Abstract):
211
+ def __new__(cls: type[FixedWidthIntT], value: int) -> FixedWidthIntT:
212
+ return super().__new__(cls, cls.check(value)) # type: ignore[misc]
213
+
214
+
215
+ class ClampedInt(FixedWidthInt, lang.Abstract):
216
+ def __new__(cls: type[FixedWidthIntT], value: int) -> FixedWidthIntT:
217
+ return super().__new__(cls, cls.clamp(value)) # type: ignore[misc]
218
+
219
+
220
+ class WrappedInt(FixedWidthInt, lang.Abstract):
221
+ def __new__(cls: type[FixedWidthIntT], value: int) -> FixedWidthIntT:
222
+ return super().__new__(cls, cls.wrap(value)) # type: ignore[misc]
223
+
224
+
225
+ _MODE_BASE_CLASSES: tuple[type[FixedWidthInt], ...] = (
226
+ CheckedInt,
227
+ ClampedInt,
228
+ WrappedInt,
229
+ )
230
+
231
+
232
+ #
233
+
234
+
235
+ class AnyInt8(FixedWidthInt, lang.Abstract):
236
+ BITS = 8
237
+
238
+
239
+ class AnyInt16(FixedWidthInt, lang.Abstract):
240
+ BITS = 16
241
+
242
+
243
+ class AnyInt32(FixedWidthInt, lang.Abstract):
244
+ BITS = 32
245
+
246
+
247
+ class AnyInt64(FixedWidthInt, lang.Abstract):
248
+ BITS = 64
249
+
250
+
251
+ class AnyInt128(FixedWidthInt, lang.Abstract):
252
+ BITS = 128
253
+
254
+
255
+ ##
256
+
257
+
258
+ class CheckedInt8(CheckedInt, SignedInt, AnyInt8):
259
+ pass
260
+
261
+
262
+ class CheckedInt16(CheckedInt, SignedInt, AnyInt16):
263
+ pass
264
+
265
+
266
+ class CheckedInt32(CheckedInt, SignedInt, AnyInt32):
267
+ pass
268
+
269
+
270
+ class CheckedInt64(CheckedInt, SignedInt, AnyInt64):
271
+ pass
272
+
273
+
274
+ class CheckedInt128(CheckedInt, SignedInt, AnyInt128):
275
+ pass
276
+
277
+
278
+ #
279
+
280
+
281
+ class CheckedUInt8(CheckedInt, UnsignedInt, AnyInt8):
282
+ pass
283
+
284
+
285
+ class CheckedUInt16(CheckedInt, UnsignedInt, AnyInt16):
286
+ pass
287
+
288
+
289
+ class CheckedUInt32(CheckedInt, UnsignedInt, AnyInt32):
290
+ pass
291
+
292
+
293
+ class CheckedUInt64(CheckedInt, UnsignedInt, AnyInt64):
294
+ pass
295
+
296
+
297
+ class CheckedUInt128(CheckedInt, UnsignedInt, AnyInt128):
298
+ pass
299
+
300
+
301
+ #
302
+
303
+
304
+ class ClampedInt8(ClampedInt, SignedInt, AnyInt8):
305
+ pass
306
+
307
+
308
+ class ClampedInt16(ClampedInt, SignedInt, AnyInt16):
309
+ pass
310
+
311
+
312
+ class ClampedInt32(ClampedInt, SignedInt, AnyInt32):
313
+ pass
314
+
315
+
316
+ class ClampedInt64(ClampedInt, SignedInt, AnyInt64):
317
+ pass
318
+
319
+
320
+ class ClampedInt128(ClampedInt, SignedInt, AnyInt128):
321
+ pass
322
+
323
+
324
+ #
325
+
326
+
327
+ class ClampedUInt8(ClampedInt, UnsignedInt, AnyInt8):
328
+ pass
329
+
330
+
331
+ class ClampedUInt16(ClampedInt, UnsignedInt, AnyInt16):
332
+ pass
333
+
334
+
335
+ class ClampedUInt32(ClampedInt, UnsignedInt, AnyInt32):
336
+ pass
337
+
338
+
339
+ class ClampedUInt64(ClampedInt, UnsignedInt, AnyInt64):
340
+ pass
341
+
342
+
343
+ class ClampedUInt128(ClampedInt, UnsignedInt, AnyInt128):
344
+ pass
345
+
346
+
347
+ #
348
+
349
+
350
+ class WrappedInt8(WrappedInt, SignedInt, AnyInt8):
351
+ pass
352
+
353
+
354
+ class WrappedInt16(WrappedInt, SignedInt, AnyInt16):
355
+ pass
356
+
357
+
358
+ class WrappedInt32(WrappedInt, SignedInt, AnyInt32):
359
+ pass
360
+
361
+
362
+ class WrappedInt64(WrappedInt, SignedInt, AnyInt64):
363
+ pass
364
+
365
+
366
+ class WrappedInt128(WrappedInt, SignedInt, AnyInt128):
367
+ pass
368
+
369
+
370
+ #
371
+
372
+
373
+ class WrappedUInt8(WrappedInt, UnsignedInt, AnyInt8):
374
+ pass
375
+
376
+
377
+ class WrappedUInt16(WrappedInt, UnsignedInt, AnyInt16):
378
+ pass
379
+
380
+
381
+ class WrappedUInt32(WrappedInt, UnsignedInt, AnyInt32):
382
+ pass
383
+
384
+
385
+ class WrappedUInt64(WrappedInt, UnsignedInt, AnyInt64):
386
+ pass
387
+
388
+
389
+ class WrappedUInt128(WrappedInt, UnsignedInt, AnyInt128):
390
+ pass
omlish/math/floats.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import struct
2
2
 
3
3
 
4
+ ##
5
+
6
+
4
7
  def isclose(a: float, b: float, *, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> float:
5
8
  return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
6
9
 
@@ -0,0 +1,127 @@
1
+ """
2
+ TODO:
3
+ - reservoir
4
+ - dep tdigest?
5
+ - struct-of-arrays - array.array('f', ...) - backed SamplingHistogram
6
+ - https://docs.python.org/3/library/statistics.html
7
+ """
8
+ import contextlib
9
+ import dataclasses as dc
10
+ import operator
11
+ import random
12
+ import time
13
+ import typing as ta
14
+
15
+ from .. import check
16
+
17
+
18
+ ##
19
+
20
+
21
+ class SamplingHistogram:
22
+ @dc.dataclass(frozen=True)
23
+ class Entry:
24
+ value: float
25
+ timestamp: float
26
+
27
+ @dc.dataclass(frozen=True)
28
+ class Percentile:
29
+ p: float
30
+ value: float
31
+
32
+ @dc.dataclass(frozen=True)
33
+ class Stats:
34
+ count: int
35
+ min: float
36
+ max: float
37
+ last_percentiles: list['SamplingHistogram.Percentile']
38
+ sample_percentiles: list['SamplingHistogram.Percentile']
39
+
40
+ DEFAULT_SIZE = 1000
41
+ DEFAULT_PERCENTILES = (0.5, 0.75, 0.9, 0.95, 0.99)
42
+
43
+ def __init__(
44
+ self,
45
+ *,
46
+ size: int = DEFAULT_SIZE,
47
+ percentiles: ta.Iterable[float] | None = None,
48
+ ) -> None:
49
+ check.arg(size > 0)
50
+
51
+ super().__init__()
52
+
53
+ self._size = size
54
+ self._percentiles = list(percentiles if percentiles is not None else self.DEFAULT_PERCENTILES)
55
+
56
+ self._count = 0
57
+ self._min = float('inf')
58
+ self._max = float('-inf')
59
+
60
+ self._percentile_pos_list = [self._calc_percentile_pos(p, self._size) for p in self._percentiles]
61
+
62
+ self._ring: list[SamplingHistogram.Entry | None] = [None] * size
63
+ self._ring_pos = 0
64
+
65
+ self._sample: list[SamplingHistogram.Entry | None] = [None] * size
66
+ self._sample_pos_queue = list(reversed(range(size)))
67
+
68
+ def add(self, value: float) -> None:
69
+ self._count += 1
70
+ self._min = min(self._min, value)
71
+ self._max = max(self._max, value)
72
+
73
+ entry = self.Entry(value, time.time())
74
+
75
+ self._ring[self._ring_pos] = entry
76
+ next_ring_pos = self._ring_pos + 1
77
+ self._ring_pos = 0 if next_ring_pos >= self._size else next_ring_pos
78
+
79
+ sample_pos = None
80
+ if self._sample_pos_queue:
81
+ with contextlib.suppress(IndexError):
82
+ sample_pos = self._sample_pos_queue.pop()
83
+ if sample_pos is None:
84
+ sample_pos = random.randrange(0, self._size)
85
+ self._sample[sample_pos] = entry
86
+
87
+ @staticmethod
88
+ def _calc_percentile_pos(p: float, sz: int) -> int:
89
+ return round((p * sz) - 1)
90
+
91
+ def _calc_percentiles(self, entries: list[Entry | None]) -> list[Percentile]:
92
+ entries = list(filter(None, entries))
93
+ sz = len(entries)
94
+ if not sz:
95
+ return []
96
+ elif sz == self._size:
97
+ pos_list = self._percentile_pos_list
98
+ else:
99
+ pos_list = [self._calc_percentile_pos(p, sz) for p in self._percentiles]
100
+ entries.sort(key=operator.attrgetter('value'))
101
+ return [
102
+ self.Percentile(p, check.not_none(entries[pos]).value)
103
+ for p, pos in zip(self._percentiles, pos_list)
104
+ ]
105
+
106
+ def get(self) -> Stats:
107
+ return self.Stats(
108
+ count=self._count,
109
+ min=self._min,
110
+ max=self._max,
111
+ last_percentiles=self._calc_percentiles(self._ring),
112
+ sample_percentiles=self._calc_percentiles(self._sample),
113
+ )
114
+
115
+ def get_filtered(self, entry_filter: ta.Callable[[Entry], bool]) -> Stats:
116
+ def filter_entries(l):
117
+ return [e for e in list(l) if e is not None and entry_filter(e)]
118
+ return self.Stats(
119
+ count=self._count,
120
+ min=self._min,
121
+ max=self._max,
122
+ last_percentiles=self._calc_percentiles(filter_entries(self._ring)),
123
+ sample_percentiles=self._calc_percentiles(filter_entries(self._sample)),
124
+ )
125
+
126
+ def get_since(self, min_timestamp: float) -> Stats:
127
+ return self.get_filtered(lambda e: e.timestamp >= min_timestamp)
omlish/math/stats.py CHANGED
@@ -1,18 +1,6 @@
1
- """
2
- TODO:
3
- - reservoir
4
- - dep tdigest?
5
- - struct-of-arrays - array.array('f', ...) - backed SamplingHistogram
6
- - https://docs.python.org/3/library/statistics.html
7
- """
8
1
  import bisect
9
2
  import collections
10
- import contextlib
11
- import dataclasses as dc
12
3
  import math
13
- import operator
14
- import random
15
- import time
16
4
  import typing as ta
17
5
 
18
6
  from .. import cached
@@ -228,115 +216,3 @@ class Stats(ta.Sequence[float]):
228
216
  count_map = collections.Counter(idxs)
229
217
  bin_counts = [(b, count_map.get(i, 0)) for i, b in enumerate(bins)]
230
218
  return bin_counts
231
-
232
-
233
- ##
234
-
235
-
236
- class SamplingHistogram:
237
- @dc.dataclass(frozen=True)
238
- class Entry:
239
- value: float
240
- timestamp: float
241
-
242
- @dc.dataclass(frozen=True)
243
- class Percentile:
244
- p: float
245
- value: float
246
-
247
- @dc.dataclass(frozen=True)
248
- class Stats:
249
- count: int
250
- min: float
251
- max: float
252
- last_percentiles: list['SamplingHistogram.Percentile']
253
- sample_percentiles: list['SamplingHistogram.Percentile']
254
-
255
- DEFAULT_SIZE = 1000
256
- DEFAULT_PERCENTILES = (0.5, 0.75, 0.9, 0.95, 0.99)
257
-
258
- def __init__(
259
- self,
260
- *,
261
- size: int = DEFAULT_SIZE,
262
- percentiles: ta.Iterable[float] | None = None,
263
- ) -> None:
264
- check.arg(size > 0)
265
-
266
- super().__init__()
267
-
268
- self._size = size
269
- self._percentiles = list(percentiles if percentiles is not None else self.DEFAULT_PERCENTILES)
270
-
271
- self._count = 0
272
- self._min = float('inf')
273
- self._max = float('-inf')
274
-
275
- self._percentile_pos_list = [self._calc_percentile_pos(p, self._size) for p in self._percentiles]
276
-
277
- self._ring: list[SamplingHistogram.Entry | None] = [None] * size
278
- self._ring_pos = 0
279
-
280
- self._sample: list[SamplingHistogram.Entry | None] = [None] * size
281
- self._sample_pos_queue = list(reversed(range(size)))
282
-
283
- def add(self, value: float) -> None:
284
- self._count += 1
285
- self._min = min(self._min, value)
286
- self._max = max(self._max, value)
287
-
288
- entry = self.Entry(value, time.time())
289
-
290
- self._ring[self._ring_pos] = entry
291
- next_ring_pos = self._ring_pos + 1
292
- self._ring_pos = 0 if next_ring_pos >= self._size else next_ring_pos
293
-
294
- sample_pos = None
295
- if self._sample_pos_queue:
296
- with contextlib.suppress(IndexError):
297
- sample_pos = self._sample_pos_queue.pop()
298
- if sample_pos is None:
299
- sample_pos = random.randrange(0, self._size)
300
- self._sample[sample_pos] = entry
301
-
302
- @staticmethod
303
- def _calc_percentile_pos(p: float, sz: int) -> int:
304
- return round((p * sz) - 1)
305
-
306
- def _calc_percentiles(self, entries: list[Entry | None]) -> list[Percentile]:
307
- entries = list(filter(None, entries))
308
- sz = len(entries)
309
- if not sz:
310
- return []
311
- elif sz == self._size:
312
- pos_list = self._percentile_pos_list
313
- else:
314
- pos_list = [self._calc_percentile_pos(p, sz) for p in self._percentiles]
315
- entries.sort(key=operator.attrgetter('value'))
316
- return [
317
- self.Percentile(p, check.not_none(entries[pos]).value)
318
- for p, pos in zip(self._percentiles, pos_list)
319
- ]
320
-
321
- def get(self) -> Stats:
322
- return self.Stats(
323
- count=self._count,
324
- min=self._min,
325
- max=self._max,
326
- last_percentiles=self._calc_percentiles(self._ring),
327
- sample_percentiles=self._calc_percentiles(self._sample),
328
- )
329
-
330
- def get_filtered(self, entry_filter: ta.Callable[[Entry], bool]) -> Stats:
331
- def filter_entries(l):
332
- return [e for e in list(l) if e is not None and entry_filter(e)]
333
- return self.Stats(
334
- count=self._count,
335
- min=self._min,
336
- max=self._max,
337
- last_percentiles=self._calc_percentiles(filter_entries(self._ring)),
338
- sample_percentiles=self._calc_percentiles(filter_entries(self._sample)),
339
- )
340
-
341
- def get_since(self, min_timestamp: float) -> Stats:
342
- return self.get_filtered(lambda e: e.timestamp >= min_timestamp)
omlish/sql/__init__.py CHANGED
@@ -1,3 +1,14 @@
1
+ # ruff: noqa: I001
2
+ import typing as _ta
3
+
4
+ from .. import lang as _lang
5
+
6
+
7
+ if _ta.TYPE_CHECKING:
8
+ from . import api
9
+ else:
10
+ api = _lang.proxy_import('.api', __package__)
11
+
1
12
  from .dbs import ( # noqa
2
13
  DbLoc,
3
14
  DbSpec,
@@ -11,3 +22,8 @@ from .qualifiedname import ( # noqa
11
22
  QualifiedName,
12
23
  qn,
13
24
  )
25
+
26
+ if _ta.TYPE_CHECKING:
27
+ from . import queries
28
+ else:
29
+ queries = _lang.proxy_import('.queries', __package__)
@@ -14,6 +14,13 @@ from .columns import ( # noqa
14
14
  Columns,
15
15
  )
16
16
 
17
+ from .dbapi import ( # noqa
18
+ DbapiRows,
19
+ DbapiConn,
20
+ DbapiDb,
21
+ DbapiAdapter,
22
+ )
23
+
17
24
  from .errors import ( # noqa
18
25
  Error,
19
26