omlish 0.0.0.dev80__py3-none-any.whl → 0.0.0.dev81__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/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev80'
2
- __revision__ = '7640968fc4c06ed0d54ea53c36daae3574c9071f'
1
+ __version__ = '0.0.0.dev81'
2
+ __revision__ = 'a6b71055e7603077b28ba1999d389905e5663aac'
3
3
 
4
4
 
5
5
  #
@@ -45,7 +45,7 @@ class Project(ProjectBase):
45
45
  'lz4 ~= 4.3',
46
46
  # 'lz4 @ git+https://github.com/wrmsr/python-lz4@wrmsr_20240830_GIL_NOT_USED'
47
47
 
48
- 'python-snappy ~= 0.7; python_version < "3.13"',
48
+ 'python-snappy ~= 0.7',
49
49
 
50
50
  'zstd ~= 1.5',
51
51
  ],
@@ -97,7 +97,7 @@ class Project(ProjectBase):
97
97
 
98
98
  'aiomysql ~= 0.2',
99
99
  'aiosqlite ~= 0.20',
100
- 'asyncpg ~= 0.30; python_version < "3.13"',
100
+ 'asyncpg ~= 0.30',
101
101
 
102
102
  'apsw ~= 3.46',
103
103
 
@@ -1,15 +1,21 @@
1
1
  import argparse
2
+ import codecs
2
3
  import contextlib
3
4
  import dataclasses as dc
4
5
  import enum
6
+ import io
5
7
  import json
8
+ import os
6
9
  import subprocess
7
10
  import sys
8
11
  import typing as ta
9
12
 
13
+ from ... import check
10
14
  from ... import lang
11
15
  from ... import term
12
16
  from .render import JsonRenderer
17
+ from .stream import JsonStreamLexer
18
+ from .stream import JsonStreamValueBuilder
13
19
 
14
20
 
15
21
  if ta.TYPE_CHECKING:
@@ -67,15 +73,25 @@ def _main() -> None:
67
73
  parser = argparse.ArgumentParser()
68
74
 
69
75
  parser.add_argument('file', nargs='?')
76
+
77
+ parser.add_argument('--stream', action='store_true')
78
+ parser.add_argument('--stream-buffer-size', type=int, default=0x1000)
79
+
70
80
  parser.add_argument('-f', '--format')
81
+
71
82
  parser.add_argument('-z', '--compact', action='store_true')
72
83
  parser.add_argument('-p', '--pretty', action='store_true')
73
84
  parser.add_argument('-i', '--indent')
74
85
  parser.add_argument('-s', '--sort-keys', action='store_true')
86
+
75
87
  parser.add_argument('-c', '--color', action='store_true')
88
+
76
89
  parser.add_argument('-l', '--less', action='store_true')
90
+
77
91
  args = parser.parse_args()
78
92
 
93
+ #
94
+
79
95
  separators = None
80
96
  if args.compact:
81
97
  separators = (',', ':')
@@ -89,6 +105,28 @@ def _main() -> None:
89
105
  except ValueError:
90
106
  indent = args.indent
91
107
 
108
+ kw: dict[str, ta.Any] = dict(
109
+ indent=indent,
110
+ separators=separators,
111
+ sort_keys=args.sort_keys,
112
+ )
113
+
114
+ def render_one(v: ta.Any) -> str:
115
+ if args.color:
116
+ return JsonRenderer.render_str(
117
+ v,
118
+ **kw,
119
+ style=term_color,
120
+ )
121
+
122
+ else:
123
+ return json.dumps(
124
+ v,
125
+ **kw,
126
+ )
127
+
128
+ #
129
+
92
130
  fmt_name = args.format
93
131
  if fmt_name is None:
94
132
  if args.file is not None:
@@ -99,45 +137,71 @@ def _main() -> None:
99
137
  fmt_name = 'json'
100
138
  fmt = FORMATS_BY_NAME[fmt_name]
101
139
 
140
+ if args.stream:
141
+ check.arg(fmt is Formats.JSON.value)
142
+
143
+ #
144
+
102
145
  with contextlib.ExitStack() as es:
103
146
  if args.file is None:
104
- in_file = sys.stdin
147
+ in_file = sys.stdin.buffer
148
+
105
149
  else:
106
- in_file = es.enter_context(open(args.file))
150
+ in_file = es.enter_context(open(args.file, 'rb'))
107
151
 
108
- data = fmt.load(in_file)
152
+ #
109
153
 
110
- kw: dict[str, ta.Any] = dict(
111
- indent=indent,
112
- separators=separators,
113
- sort_keys=args.sort_keys,
114
- )
154
+ if args.less:
155
+ less = subprocess.Popen(
156
+ [
157
+ 'less',
158
+ *(['-R'] if args.color else []),
159
+ ],
160
+ stdin=subprocess.PIPE,
161
+ encoding='utf-8',
162
+ )
163
+ out = check.not_none(less.stdin)
115
164
 
116
- if args.color:
117
- out = JsonRenderer.render_str(
118
- data,
119
- **kw,
120
- style=term_color,
121
- )
165
+ def close_less():
166
+ out.close()
167
+ less.wait()
122
168
 
123
- else:
124
- out = json.dumps(
125
- data,
126
- **kw,
127
- )
128
-
129
- if args.less:
130
- subprocess.run(
131
- [
132
- 'less',
133
- *(['-R'] if args.color else []),
134
- ],
135
- input=out.encode(),
136
- check=True,
137
- )
169
+ es.enter_context(lang.defer(close_less)) # noqa
138
170
 
139
- else:
140
- print(out)
171
+ else:
172
+ out = sys.stdout
173
+
174
+ #
175
+
176
+ if args.stream:
177
+ fd = in_file.fileno()
178
+ decoder = codecs.getincrementaldecoder('utf-8')()
179
+
180
+ with contextlib.ExitStack() as es2:
181
+ lex = es2.enter_context(JsonStreamLexer())
182
+ vb = es2.enter_context(JsonStreamValueBuilder())
183
+
184
+ while True:
185
+ buf = os.read(fd, args.stream_buffer_size)
186
+
187
+ for s in decoder.decode(buf, not buf):
188
+ n = 0
189
+ for c in s:
190
+ for t in lex(c):
191
+ for v in vb(t):
192
+ print(render_one(v), file=out)
193
+ n += 1
194
+
195
+ if n:
196
+ out.flush()
197
+
198
+ if not buf:
199
+ break
200
+
201
+ else:
202
+ with io.TextIOWrapper(in_file) as tw:
203
+ v = fmt.load(tw)
204
+ print(render_one(v), file=out)
141
205
 
142
206
 
143
207
  if __name__ == '__main__':
@@ -1,3 +1,7 @@
1
+ """
2
+ TODO:
3
+ - genmachine...
4
+ """
1
5
  import enum
2
6
  import io
3
7
  import json
@@ -0,0 +1,389 @@
1
+ import dataclasses as dc
2
+ import io
3
+ import json
4
+ import re
5
+ import typing as ta
6
+
7
+ from ... import check
8
+ from ...genmachine import GenMachine
9
+
10
+
11
+ ValueTokenKind: ta.TypeAlias = ta.Literal[
12
+ 'STRING',
13
+ 'NUMBER',
14
+
15
+ 'SPECIAL_NUMBER',
16
+ 'BOOLEAN',
17
+ 'NULL',
18
+ ]
19
+
20
+ ControlTokenKind: ta.TypeAlias = ta.Literal[
21
+ 'LBRACE',
22
+ 'RBRACE',
23
+ 'LBRACKET',
24
+ 'RBRACKET',
25
+ 'COMMA',
26
+ 'COLON',
27
+ ]
28
+
29
+ TokenKind: ta.TypeAlias = ValueTokenKind | ControlTokenKind
30
+
31
+ TokenValue: ta.TypeAlias = str | float | int | None
32
+
33
+
34
+ class Token(ta.NamedTuple):
35
+ kind: TokenKind
36
+ value: TokenValue
37
+ raw: str
38
+
39
+ ofs: int
40
+ line: int
41
+ col: int
42
+
43
+ def __iter__(self):
44
+ raise TypeError
45
+
46
+
47
+ NUMBER_PAT = re.compile(r'-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?')
48
+
49
+ VALUE_TOKEN_KINDS = frozenset(check.isinstance(a, str) for a in ta.get_args(ValueTokenKind))
50
+
51
+ CONTROL_TOKENS: ta.Mapping[str, TokenKind] = {
52
+ '{': 'LBRACE',
53
+ '}': 'RBRACE',
54
+ '[': 'LBRACKET',
55
+ ']': 'RBRACKET',
56
+ ',': 'COMMA',
57
+ ':': 'COLON',
58
+ }
59
+
60
+ CONST_TOKENS: ta.Mapping[str, tuple[TokenKind, str | float | None]] = {
61
+ 'NaN': ('SPECIAL_NUMBER', float('nan')),
62
+ 'Infinity': ('SPECIAL_NUMBER', float('inf')),
63
+ '-Infinity': ('SPECIAL_NUMBER', float('-inf')),
64
+
65
+ 'true': ('BOOLEAN', True),
66
+ 'false': ('BOOLEAN', False),
67
+ 'null': ('NULL', None),
68
+ }
69
+
70
+
71
+ @dc.dataclass(frozen=True)
72
+ class JsonLexError(Exception):
73
+ message: str
74
+
75
+ ofs: int
76
+ line: int
77
+ col: int
78
+
79
+
80
+ class JsonStreamLexer(GenMachine[str, Token]):
81
+ def __init__(self) -> None:
82
+ self._ofs = 0
83
+ self._line = 0
84
+ self._col = 0
85
+
86
+ self._buf = io.StringIO()
87
+
88
+ super().__init__(self._do_main())
89
+
90
+ def _char_in(self, c: str) -> str:
91
+ if len(c) != 1:
92
+ raise ValueError(c)
93
+
94
+ self._ofs += 1
95
+
96
+ if c == '\n':
97
+ self._line += 1
98
+ self._col = 0
99
+ else:
100
+ self._col += 1
101
+
102
+ return c
103
+
104
+ def _make_tok(
105
+ self,
106
+ kind: TokenKind,
107
+ value: TokenValue,
108
+ raw: str,
109
+ ) -> ta.Sequence[Token]:
110
+ tok = Token(
111
+ kind,
112
+ value,
113
+ raw,
114
+ self._ofs,
115
+ self._line,
116
+ self._col,
117
+ )
118
+ return (tok,)
119
+
120
+ def _flip_buf(self) -> str:
121
+ raw = self._buf.getvalue()
122
+ self._buf.seek(0)
123
+ self._buf.truncate()
124
+ return raw
125
+
126
+ def _raise(self, msg: str) -> ta.NoReturn:
127
+ raise JsonLexError(msg, self._ofs, self._line, self._col)
128
+
129
+ def _do_main(self):
130
+ while True:
131
+ c = self._char_in((yield None)) # noqa
132
+
133
+ if c.isspace():
134
+ continue
135
+
136
+ if c in CONTROL_TOKENS:
137
+ yield self._make_tok(CONTROL_TOKENS[c], c, c)
138
+ continue
139
+
140
+ if c == '"':
141
+ return self._do_string()
142
+
143
+ if c.isdigit() or c == '-':
144
+ return self._do_number(c)
145
+
146
+ if c in 'tfnIN':
147
+ return self._do_const(c)
148
+
149
+ self._raise(f'Unexpected character: {c}')
150
+
151
+ def _do_string(self):
152
+ self._buf.write('"')
153
+
154
+ last = None
155
+ while True:
156
+ try:
157
+ c = self._char_in((yield None)) # noqa
158
+ except GeneratorExit:
159
+ self._raise('Unexpected end of input')
160
+
161
+ self._buf.write(c)
162
+ if c == '"' and last != '\\':
163
+ break
164
+ last = c
165
+
166
+ raw = self._flip_buf()
167
+ sv = json.loads(raw)
168
+ yield self._make_tok('STRING', sv, raw)
169
+
170
+ return self._do_main()
171
+
172
+ def _do_number(self, c: str):
173
+ self._buf.write(c)
174
+
175
+ while True:
176
+ try:
177
+ c = self._char_in((yield None)) # noqa
178
+ except GeneratorExit:
179
+ self._raise('Unexpected end of input')
180
+
181
+ if not (c.isdigit() or c in '.eE+-'):
182
+ break
183
+ self._buf.write(c)
184
+
185
+ raw = self._flip_buf()
186
+ if not NUMBER_PAT.fullmatch(raw):
187
+ raw += c
188
+ try:
189
+ for _ in range(7):
190
+ raw += self._char_in((yield None)) # noqa
191
+ except GeneratorExit:
192
+ self._raise('Unexpected end of input')
193
+
194
+ if raw != '-Infinity':
195
+ self._raise(f'Invalid number format: {raw}')
196
+
197
+ tk, tv = CONST_TOKENS[raw]
198
+ yield self._make_tok(tk, tv, raw)
199
+
200
+ return self._do_main()
201
+
202
+ nv = float(raw) if '.' in raw or 'e' in raw or 'E' in raw else int(raw)
203
+ yield self._make_tok('NUMBER', nv, raw)
204
+
205
+ if c in CONTROL_TOKENS:
206
+ yield self._make_tok(CONTROL_TOKENS[c], c, c)
207
+ elif not c.isspace():
208
+ self._raise(f'Unexpected character after number: {c}')
209
+
210
+ return self._do_main()
211
+
212
+ def _do_const(self, c: str):
213
+ raw = c
214
+ while True:
215
+ try:
216
+ raw += self._char_in((yield None)) # noqa
217
+ except GeneratorExit:
218
+ self._raise('Unexpected end of input')
219
+
220
+ if raw in CONST_TOKENS:
221
+ break
222
+
223
+ if len(raw) > 8: # None of the keywords are longer than 8 characters
224
+ self._raise(f'Invalid literal: {raw}')
225
+
226
+ tk, tv = CONST_TOKENS[raw]
227
+ yield self._make_tok(tk, tv, raw)
228
+
229
+ return self._do_main()
230
+
231
+
232
+ class JsonStreamObject(list):
233
+ def __repr__(self) -> str:
234
+ return f'{self.__class__.__name__}({super().__repr__()})'
235
+
236
+
237
+ class JsonStreamValueBuilder(GenMachine[Token, ta.Any]):
238
+ def __init__(
239
+ self,
240
+ *,
241
+ yield_object_lists: bool = False,
242
+ ) -> None:
243
+ super().__init__(self._do_value())
244
+
245
+ self._yield_object_lists = yield_object_lists
246
+
247
+ self._stack: list[
248
+ tuple[ta.Literal['OBJECT'], JsonStreamObject] |
249
+ tuple[ta.Literal['PAIR'], str] |
250
+ tuple[ta.Literal['ARRAY'], list]
251
+ ] = []
252
+
253
+ #
254
+
255
+ def _emit_value(self, v):
256
+ if not self._stack:
257
+ return ((v,), self._do_value())
258
+
259
+ tt, tv = self._stack[-1]
260
+ if tt == 'PAIR':
261
+ self._stack.pop()
262
+ if not self._stack:
263
+ raise self.StateError
264
+
265
+ tt2, tv2 = self._stack[-1]
266
+ if tt2 == 'OBJECT':
267
+ tv2.append((tv, v)) # type: ignore
268
+ return ((), self._do_after_pair())
269
+
270
+ else:
271
+ raise self.StateError
272
+
273
+ elif tt == 'ARRAY':
274
+ tv.append(v) # type: ignore
275
+ return ((), self._do_after_element())
276
+
277
+ else:
278
+ raise self.StateError
279
+
280
+ #
281
+
282
+ def _do_value(self):
283
+ try:
284
+ tok = yield None
285
+ except GeneratorExit:
286
+ if self._stack:
287
+ raise self.StateError from None
288
+ else:
289
+ raise
290
+
291
+ if tok.kind in VALUE_TOKEN_KINDS:
292
+ y, r = self._emit_value(tok.value)
293
+ yield y
294
+ return r
295
+
296
+ elif tok.kind == 'LBRACE':
297
+ return self._do_object()
298
+
299
+ elif tok.kind == 'LBRACKET':
300
+ return self._do_array()
301
+
302
+ else:
303
+ raise self.StateError
304
+
305
+ #
306
+
307
+ def _do_object(self):
308
+ self._stack.append(('OBJECT', JsonStreamObject()))
309
+ return self._do_object_body()
310
+
311
+ def _do_object_body(self):
312
+ try:
313
+ tok = yield None
314
+ except GeneratorExit:
315
+ raise self.StateError from None
316
+
317
+ if tok.kind == 'STRING':
318
+ k = tok.value
319
+
320
+ try:
321
+ tok = yield None
322
+ except GeneratorExit:
323
+ raise self.StateError from None
324
+ if tok.kind != 'COLON':
325
+ raise self.StateError
326
+
327
+ self._stack.append(('PAIR', k))
328
+ return self._do_value()
329
+
330
+ else:
331
+ raise self.StateError
332
+
333
+ def _do_after_pair(self):
334
+ try:
335
+ tok = yield None
336
+ except GeneratorExit:
337
+ raise self.StateError from None
338
+
339
+ if tok.kind == 'COMMA':
340
+ return self._do_object_body()
341
+
342
+ elif tok.kind == 'RBRACE':
343
+ if not self._stack:
344
+ raise self.StateError
345
+
346
+ tv: ta.Any
347
+ tt, tv = self._stack.pop()
348
+ if tt != 'OBJECT':
349
+ raise self.StateError
350
+
351
+ if not self._yield_object_lists:
352
+ tv = dict(tv)
353
+
354
+ y, r = self._emit_value(tv)
355
+ yield y
356
+ return r
357
+
358
+ else:
359
+ raise self.StateError
360
+
361
+ #
362
+
363
+ def _do_array(self):
364
+ self._stack.append(('ARRAY', []))
365
+ return self._do_value()
366
+
367
+ def _do_after_element(self):
368
+ try:
369
+ tok = yield None
370
+ except GeneratorExit:
371
+ raise self.StateError from None
372
+
373
+ if tok.kind == 'COMMA':
374
+ return self._do_value()
375
+
376
+ elif tok.kind == 'RBRACKET':
377
+ if not self._stack:
378
+ raise self.StateError
379
+
380
+ tt, tv = self._stack.pop()
381
+ if tt != 'ARRAY':
382
+ raise self.StateError
383
+
384
+ y, r = self._emit_value(tv)
385
+ yield y
386
+ return r
387
+
388
+ else:
389
+ raise self.StateError
omlish/genmachine.py CHANGED
@@ -1,4 +1,7 @@
1
1
  """
2
+ TODO:
3
+ - feed_iter helper
4
+
2
5
  See:
3
6
  - https://github.com/pytransitions/transitions
4
7
  """
@@ -15,10 +18,6 @@ MachineGen: ta.TypeAlias = ta.Generator[ta.Any, ta.Any, ta.Any]
15
18
  ##
16
19
 
17
20
 
18
- class IllegalStateError(Exception):
19
- pass
20
-
21
-
22
21
  class GenMachine(ta.Generic[I, O]):
23
22
  """
24
23
  Generator-powered state machine. Generators are sent an `I` object and yield any number of `O` objects in response,
@@ -30,30 +29,65 @@ class GenMachine(ta.Generic[I, O]):
30
29
  super().__init__()
31
30
  self._advance(initial)
32
31
 
32
+ _gen: MachineGen | None
33
+
34
+ def __repr__(self) -> str:
35
+ return f'{self.__class__.__name__}@{hex(id(self))[2:]}<{self.state}>'
36
+
37
+ #
38
+
33
39
  @property
34
40
  def state(self) -> str | None:
35
41
  if self._gen is not None:
36
42
  return self._gen.gi_code.co_qualname
37
43
  return None
38
44
 
39
- def __repr__(self) -> str:
40
- return f'{self.__class__.__name__}@{hex(id(self))[2:]}<{self.state}>'
45
+ #
41
46
 
42
- _gen: MachineGen | None
47
+ @property
48
+ def closed(self) -> bool:
49
+ return self._gen is None
50
+
51
+ def close(self) -> None:
52
+ if self._gen is not None:
53
+ self._gen.close()
54
+ self._gen = None
55
+
56
+ def __enter__(self) -> ta.Self:
57
+ return self
58
+
59
+ def __exit__(self, exc_type, exc_val, exc_tb):
60
+ self.close()
61
+
62
+ #
63
+
64
+ class Error(Exception):
65
+ pass
66
+
67
+ class ClosedError(Exception):
68
+ pass
69
+
70
+ class StateError(Exception):
71
+ pass
72
+
73
+ #
43
74
 
44
75
  def _advance(self, gen: MachineGen) -> None:
45
76
  self._gen = gen
46
77
  if (n := next(self._gen)) is not None: # noqa
47
- raise IllegalStateError
78
+ raise GenMachine.ClosedError
48
79
 
49
80
  def __call__(self, i: I) -> ta.Iterable[O]:
50
81
  if self._gen is None:
51
- raise IllegalStateError
82
+ raise GenMachine.ClosedError
83
+
52
84
  try:
53
85
  while (o := self._gen.send(i)) is not None:
54
86
  yield from o
87
+
55
88
  except StopIteration as s:
56
89
  if s.value is None:
57
90
  self._gen = None
58
91
  return None
92
+
59
93
  self._advance(s.value)
omlish/lang/resources.py CHANGED
@@ -1,14 +1,19 @@
1
+ import dataclasses as dc
1
2
  import functools
2
3
  import importlib.resources
3
4
  import os.path
4
5
  import typing as ta
5
6
 
6
7
 
7
- class RelativeResource(ta.NamedTuple):
8
+ @dc.dataclass(frozen=True)
9
+ class RelativeResource:
8
10
  name: str
9
11
  is_file: bool
10
12
  read_bytes: ta.Callable[[], bytes]
11
13
 
14
+ def read_text(self, encoding: str = 'utf-8') -> str:
15
+ return self.read_bytes().decode(encoding)
16
+
12
17
 
13
18
  def get_relative_resources(
14
19
  path: str = '',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev80
3
+ Version: 0.0.0.dev81
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -19,6 +19,7 @@ Requires-Dist: greenlet ~=3.1 ; extra == 'all'
19
19
  Requires-Dist: trio ~=0.27 ; extra == 'all'
20
20
  Requires-Dist: trio-asyncio ~=0.15 ; extra == 'all'
21
21
  Requires-Dist: lz4 ~=4.3 ; extra == 'all'
22
+ Requires-Dist: python-snappy ~=0.7 ; extra == 'all'
22
23
  Requires-Dist: zstd ~=1.5 ; extra == 'all'
23
24
  Requires-Dist: asttokens ~=2.4 ; extra == 'all'
24
25
  Requires-Dist: executing ~=2.1 ; extra == 'all'
@@ -37,11 +38,10 @@ Requires-Dist: pg8000 ~=1.31 ; extra == 'all'
37
38
  Requires-Dist: pymysql ~=1.1 ; extra == 'all'
38
39
  Requires-Dist: aiomysql ~=0.2 ; extra == 'all'
39
40
  Requires-Dist: aiosqlite ~=0.20 ; extra == 'all'
41
+ Requires-Dist: asyncpg ~=0.30 ; extra == 'all'
40
42
  Requires-Dist: apsw ~=3.46 ; extra == 'all'
41
43
  Requires-Dist: duckdb ~=1.1 ; extra == 'all'
42
44
  Requires-Dist: pytest ~=8.0 ; extra == 'all'
43
- Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'all'
44
- Requires-Dist: asyncpg ~=0.30 ; (python_version < "3.13") and extra == 'all'
45
45
  Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'all'
46
46
  Provides-Extra: async
47
47
  Requires-Dist: anyio ~=4.6 ; extra == 'async'
@@ -51,8 +51,8 @@ Requires-Dist: trio ~=0.27 ; extra == 'async'
51
51
  Requires-Dist: trio-asyncio ~=0.15 ; extra == 'async'
52
52
  Provides-Extra: compress
53
53
  Requires-Dist: lz4 ~=4.3 ; extra == 'compress'
54
+ Requires-Dist: python-snappy ~=0.7 ; extra == 'compress'
54
55
  Requires-Dist: zstd ~=1.5 ; extra == 'compress'
55
- Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'compress'
56
56
  Provides-Extra: diag
57
57
  Requires-Dist: asttokens ~=2.4 ; extra == 'diag'
58
58
  Requires-Dist: executing ~=2.1 ; extra == 'diag'
@@ -85,9 +85,9 @@ Requires-Dist: pg8000 ~=1.31 ; extra == 'sqldrivers'
85
85
  Requires-Dist: pymysql ~=1.1 ; extra == 'sqldrivers'
86
86
  Requires-Dist: aiomysql ~=0.2 ; extra == 'sqldrivers'
87
87
  Requires-Dist: aiosqlite ~=0.20 ; extra == 'sqldrivers'
88
+ Requires-Dist: asyncpg ~=0.30 ; extra == 'sqldrivers'
88
89
  Requires-Dist: apsw ~=3.46 ; extra == 'sqldrivers'
89
90
  Requires-Dist: duckdb ~=1.1 ; extra == 'sqldrivers'
90
- Requires-Dist: asyncpg ~=0.30 ; (python_version < "3.13") and extra == 'sqldrivers'
91
91
  Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'sqldrivers'
92
92
  Provides-Extra: testing
93
93
  Requires-Dist: pytest ~=8.0 ; extra == 'testing'
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=ucaSu1XcJPryi-AqINUejkVDeJAFk7Bp5ar5_tJTgME,1692
2
- omlish/__about__.py,sha256=SnFS7MrReE5zY7UlXoamcmfZGpSft2Av718niU-K2ho,3420
2
+ omlish/__about__.py,sha256=ShI7he6zZJPcB446sYRskzCJpPLVOwTUy-2gII0BYpI,3370
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
5
5
  omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
@@ -10,7 +10,7 @@ omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
10
10
  omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
11
11
  omlish/fnpairs.py,sha256=Sl8CMFNyDS-1JYAjSWqnT5FmUm9Lj6o7FxSRo7g4jww,10875
12
12
  omlish/fnpipes.py,sha256=AJkgz9nvRRm7oqw7ZgYyz21klu276LWi54oYCLg-vOg,2196
13
- omlish/genmachine.py,sha256=LCMiqvK32dAWtrlB6lKw9tXdQFiXC8rRdk4TMQYIroU,1603
13
+ omlish/genmachine.py,sha256=zcFUbWHlX2qqNGTb6V8i9Hmbc7sta4DOry7B67mG-uY,2092
14
14
  omlish/iterators.py,sha256=GGLC7RIT86uXMjhIIIqnff_Iu5SI_b9rXYywYGFyzmo,7292
15
15
  omlish/libc.py,sha256=8r7Ejyhttk9ruCfBkxNTrlzir5WPbDE2vmY7VPlceMA,15362
16
16
  omlish/matchfns.py,sha256=I1IlQGfEyk_AcFSy6ulVS3utC-uwyZM2YfUXYHc9Bw0,6152
@@ -184,9 +184,10 @@ omlish/formats/props.py,sha256=JwFJbKblqzqnzXf7YKFzQSDfcAXzkKsfoYvad6FPy98,18945
184
184
  omlish/formats/yaml.py,sha256=wTW8ECG9jyA7qIFUqKZUro4KAKpN4IvcW_qhlrKveXM,6836
185
185
  omlish/formats/json/__init__.py,sha256=moSR67Qkju2eYb_qVDtaivepe44mxAnYuC8OCSbtETg,298
186
186
  omlish/formats/json/__main__.py,sha256=1wxxKZVkj_u7HCcewwMIbGuZj_Wph95yrUbm474Op9M,188
187
- omlish/formats/json/cli.py,sha256=pHFvYji6h_kMUyTgHCuDFofeDVY_5Em0wBqqVOJzDmI,3504
187
+ omlish/formats/json/cli.py,sha256=FJuVzXBx9ysd31fObwGeiCyFz7F0Mx7I9nC9vbjsbCs,5195
188
188
  omlish/formats/json/json.py,sha256=y8d8WWgzGZDTjzYc_xe9v4T0foXHI-UP7gjCwnHzUIA,828
189
- omlish/formats/json/render.py,sha256=6edhSrxXWW3nzRfokp5qaldT0_YAj-HVEan_rErf-vo,3208
189
+ omlish/formats/json/render.py,sha256=7p-Q_nq9MwmlZkKHVDY58yVCkztePeZXnimiROoMDko,3239
190
+ omlish/formats/json/stream.py,sha256=oGP91ivGEoPzC1Vv-cMDZHCqnt2Cq4WLswYTv39Bayk,9167
190
191
  omlish/formats/json/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
192
  omlish/formats/json/backends/orjson.py,sha256=GYZx0zgpxwkJbFh4EJLGa6VMoEK-Q6mf5tQp8aXqTDc,2526
192
193
  omlish/formats/json/backends/std.py,sha256=00NdUFT9GeWL1EWbgKhWLboDBIuDxr7EiizPZXbRWrc,1973
@@ -257,7 +258,7 @@ omlish/lang/iterables.py,sha256=xRwktm6i2RHSb_ELfAXdjITIfE69qDyMEzgeZqvQXiU,2386
257
258
  omlish/lang/maybes.py,sha256=NYHZDjqDtwPMheDrj2VtUVujxRPf8Qpgk4ZlZCTvBZc,3492
258
259
  omlish/lang/objects.py,sha256=LOC3JvX1g5hPxJ7Sv2TK9kNkAo9c8J-Jw2NmClR_rkA,4576
259
260
  omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
260
- omlish/lang/resources.py,sha256=-NmVTrSMKFZ6smVfOMz46ekZYVGgYh8cPooxQlFpG6s,2135
261
+ omlish/lang/resources.py,sha256=yywDWhh0tsgw24l7mHYv49ll4oZS8Kc8MSCa8f4UbbI,2280
261
262
  omlish/lang/strings.py,sha256=BsciSYnckD4vGtC6kmtnugR9IN6CIHdcjO4nZu-pSAw,3898
262
263
  omlish/lang/sys.py,sha256=UoZz_PJYVKLQAKqYxxn-LHz1okK_38I__maZgnXMcxU,406
263
264
  omlish/lang/timeouts.py,sha256=vECdWYhc_IZgcal1Ng1Y42wf2FV3KAx-i8As-MgGHIQ,1186
@@ -439,9 +440,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
439
440
  omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
440
441
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
441
442
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
442
- omlish-0.0.0.dev80.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
443
- omlish-0.0.0.dev80.dist-info/METADATA,sha256=C5-hgQEhvnVcEKJZoj66bI0e_vNVBGovLqsqsvZHC54,4167
444
- omlish-0.0.0.dev80.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
445
- omlish-0.0.0.dev80.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
446
- omlish-0.0.0.dev80.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
447
- omlish-0.0.0.dev80.dist-info/RECORD,,
443
+ omlish-0.0.0.dev81.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
444
+ omlish-0.0.0.dev81.dist-info/METADATA,sha256=UooryMHMgAq8_m6upiqUVBeYA2q0sed0HRF4gxG3I8A,4047
445
+ omlish-0.0.0.dev81.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
446
+ omlish-0.0.0.dev81.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
447
+ omlish-0.0.0.dev81.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
448
+ omlish-0.0.0.dev81.dist-info/RECORD,,