omlish 0.0.0.dev14__py3-none-any.whl → 0.0.0.dev15__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.dev14'
2
- __revision__ = '6d7ee2004110d9e34b2463374a8ec3961c87e8e2'
1
+ __version__ = '0.0.0.dev15'
2
+ __revision__ = '52225b19abf6c8dc3f10bdccebdc0a7ca9f48ed1'
3
3
 
4
4
 
5
5
  #
@@ -50,10 +50,15 @@ class Project(ProjectBase):
50
50
  ],
51
51
 
52
52
  'formats': [
53
- 'cloudpickle ~= 3.0',
53
+ 'orjson ~= 3.10',
54
+ # 'python-rapidjson ~= 1.18',
55
+ # 'ujson ~= 5.10',
56
+
54
57
  'json5 ~= 0.9',
55
- 'orjson > 3.10',
58
+
56
59
  'pyyaml ~= 5.0',
60
+
61
+ 'cloudpickle ~= 3.0',
57
62
  ],
58
63
 
59
64
  'http': [
omlish/bootstrap.py CHANGED
@@ -2,6 +2,15 @@
2
2
  TODO:
3
3
  - more logging options
4
4
  - a more powerful interface would be run_fn_with_bootstrap..
5
+ - ./python -m gprof2dot -f pstats prof.pstats | dot -Tpdf -o prof.pstats.pdf && open prof.pstats.pdf
6
+
7
+ TODO diag:
8
+ - yappi
9
+ - stackscope
10
+ - https://github.com/pythonspeed/filprofiler
11
+ - https://pypi.org/project/guppy3/
12
+ - https://pypi.org/project/memory-profiler/
13
+ - https://pypi.org/project/Pympler/
5
14
 
6
15
  TODO new items:
7
16
  - pydevd connect-back
omlish/concurrent.py CHANGED
@@ -161,20 +161,21 @@ class ImmediateExecutor(cf.Executor):
161
161
 
162
162
 
163
163
  @contextlib.contextmanager
164
- def new_thread_or_immediate_executor(
164
+ def new_executor(
165
165
  max_workers: int | None = None,
166
+ cls: type[cf.Executor] = cf.ThreadPoolExecutor,
166
167
  *,
167
168
  immediate_exceptions: bool = False,
168
- thread_name_prefix: str = '',
169
169
  **kwargs: ta.Any,
170
170
  ) -> ta.Generator[cf.Executor, None, None]:
171
171
  if max_workers == 0:
172
172
  yield ImmediateExecutor(
173
173
  immediate_exceptions=immediate_exceptions,
174
174
  )
175
+
175
176
  else:
176
- with cf.ThreadPoolExecutor(
177
- thread_name_prefix=thread_name_prefix,
177
+ with cls( # type: ignore
178
+ max_workers,
178
179
  **kwargs,
179
180
  ) as exe:
180
181
  yield exe
omlish/formats/json.py CHANGED
@@ -1,125 +1,7 @@
1
1
  """
2
- json
3
- dump
4
- skipkeys=False
5
- ensure_ascii=True
6
- check_circular=True
7
- allow_nan=True
8
- cls=None
9
- indent=None
10
- separators=None
11
- default=None
12
- sort_keys=False
13
- dumps
14
- ^
15
- load
16
- cls=None
17
- object_hook=None
18
- parse_float=None
19
- parse_int=None
20
- parse_constant=None
21
- object_pairs_hook=None
22
- loads
23
- ^
24
-
25
- ujson
26
- dump
27
- ensure_ascii
28
- encode_html_chars
29
- escape_forward_slashes
30
- sort_keys
31
- indent
32
- allow_nan
33
- reject_bytes
34
- default
35
- separators
36
- dumps
37
- ^
38
- load
39
- loads
40
-
41
- orjson
42
- dumps
43
- default
44
- option
45
- OPT_INDENT_2
46
- OPT_NAIVE_UTC
47
- OPT_NON_STR_KEYS
48
- OPT_OMIT_MICROSECONDS
49
- OPT_PASSTHROUGH_DATACLASS
50
- OPT_PASSTHROUGH_DATETIME
51
- OPT_PASSTHROUGH_SUBCLASS
52
- OPT_SERIALIZE_DATACLASS
53
- OPT_SERIALIZE_NUMPY
54
- OPT_SERIALIZE_UUID
55
- OPT_SORT_KEYS
56
- OPT_STRICT_INTEGER
57
- OPT_UTC_Z
58
- loads
59
-
60
- rapidjson
61
- dump
62
- skipkeys=False,
63
- ensure_ascii=True,
64
- write_mode=WM_COMPACT,
65
- WM_COMPACT
66
- WM_PRETTY
67
- WM_SINGLE_LINE_ARRAY
68
- indent=4,
69
- default=None,
70
- sort_keys=False,
71
- number_mode=None,
72
- NM_NONE
73
- NM_DECIMAL
74
- NM_NAN
75
- NM_NATIVE
76
- datetime_mode=None,
77
- DM_NONE
78
- DM_ISO8601
79
- DM_UNIX_TIME
80
- DM_ONLY_SECONDS
81
- DM_IGNORE_TZ
82
- DM_NAIVE_IS_UTC
83
- DM_SHIFT_TO_UTC
84
- uuid_mode=None,
85
- UM_NONE
86
- UM_CANONICAL
87
- UM_HEX
88
- bytes_mode=BM_UTF8,
89
- BM_NONE
90
- BM_UTF8
91
- iterable_mode=IM_ANY_ITERABLE,
92
- IM_ANY_ITERABLE
93
- IM_ONLY_LISTS
94
- mapping_mode=MM_ANY_MAPPING,
95
- MM_ANY_MAPPING
96
- MM_ONLY_DICTS
97
- MM_COERCE_KEYS_TO_STRINGS
98
- MM_SKIP_NON_STRING_KEYS
99
- MM_SORT_KEYS
100
- chunk_size
101
- allow_nan=True
102
- dumps
103
- ^
104
- -chunk_size
105
- load
106
- object_hook=None,
107
- number_mode=None,
108
- ^
109
- datetime_mode=None,
110
- ^
111
- uuid_mode=None,
112
- ^
113
- parse_mode=None,
114
- PM_NONE
115
- PM_COMMENTS
116
- PM_TRAILING_COMMAS
117
- chunk_size=65536,
118
- allow_nan=True
119
- loads
120
- ^
121
- -chunk_size
122
-
2
+ TODO:
3
+ - backend abstr
4
+ - streaming
123
5
  """
124
6
  import functools
125
7
  import json as _json
@@ -173,161 +55,3 @@ COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
173
55
 
174
56
  dump_compact: ta.Callable[..., bytes] = functools.partial(dump, **COMPACT_KWARGS) # type: ignore
175
57
  dumps_compact: ta.Callable[..., str] = functools.partial(dumps, **COMPACT_KWARGS)
176
-
177
-
178
- ##
179
-
180
-
181
- # import functools
182
- # import typing as ta
183
- #
184
- # from . import cached
185
- # from . import dataclasses as dc
186
- #
187
- #
188
- # F = ta.TypeVar('F')
189
- # T = ta.TypeVar('T')
190
- # StrMap = ta.Mapping[str, ta.Any]
191
- #
192
- #
193
- # ENCODING = 'utf-8'
194
- # PRETTY_INDENT = 2
195
- # COMPACT_SEPARATORS = (',', ':')
196
- #
197
- #
198
- # Dumps = ta.Callable[[ta.Any], str]
199
- # Dumpb = ta.Callable[[ta.Any], bytes]
200
- # Loads = ta.Callable[[ta.Union[str, bytes, bytearray]], ta.Any]
201
- #
202
- #
203
- # @dc.dataclass(frozen=True)
204
- # class _Provider:
205
- # pretty_kwargs: StrMap = dc.field(default_factory=dict)
206
- # compact_kwargs: StrMap = dc.field(default_factory=dict)
207
- #
208
- #
209
- # class Provider:
210
- #
211
- # def __init__(self, json: ta.Any) -> None:
212
- # super().__init__()
213
- # self._json = json
214
- #
215
- # @property
216
- # def json(self) -> ta.Any:
217
- # return self._json
218
- #
219
- # @cached.property
220
- # def dumps(self) -> Dumps:
221
- # return self.json.dumps
222
- #
223
- # @cached.property
224
- # def dumpb(self) -> Dumpb:
225
- # def dumpb(*args, **kwargs):
226
- # return fn(*args, **kwargs).encode(ENCODING)
227
- # fn = self.json.dumps
228
- # return functools.wraps(fn)(dumpb)
229
- #
230
- # @cached.property
231
- # def loads(self) -> Loads:
232
- # return self.json.loads
233
- #
234
- # @cached.property
235
- # def pretty_kwargs(self) -> StrMap:
236
- # return {}
237
- #
238
- # @cached.property
239
- # def compact_kwargs(self) -> StrMap:
240
- # return {}
241
- #
242
- #
243
- # class OrjsonProvider(Provider):
244
- #
245
- # def __init__(self) -> None:
246
- # import orjson
247
- # super().__init__(orjson)
248
- #
249
- # @cached.property
250
- # def pretty_kwargs(self) -> StrMap:
251
- # return {
252
- # 'option': self.json.OPT_INDENT_2,
253
- # }
254
- #
255
- # @cached.property
256
- # def dumps(self) -> Dumps:
257
- # def dumps(*args, **kwargs):
258
- # return fn(*args, **kwargs).decode(ENCODING)
259
- # fn = self.json.dumps
260
- # return functools.wraps(fn)(dumps)
261
- #
262
- # @cached.property
263
- # def dumpb(self) -> Dumpb:
264
- # return self.json.dumps
265
- #
266
- #
267
- # class UjsonProvider(Provider):
268
- #
269
- # def __init__(self) -> None:
270
- # import ujson
271
- # super().__init__(ujson)
272
- #
273
- # @cached.property
274
- # def pretty_kwargs(self) -> StrMap:
275
- # return {
276
- # 'indent': PRETTY_INDENT,
277
- # }
278
- #
279
- # @cached.property
280
- # def loads(self) -> Loads:
281
- # def loads(arg, *args, **kwargs):
282
- # if isinstance(arg, (bytes, bytearray)):
283
- # arg = arg.decode(ENCODING)
284
- # return fn(arg, *args, **kwargs)
285
- # fn = self.json.loads
286
- # return functools.wraps(fn)(loads)
287
- #
288
- #
289
- # class BuiltinProvider(Provider):
290
- #
291
- # def __init__(self) -> None:
292
- # import json
293
- # super().__init__(json)
294
- #
295
- # @cached.property
296
- # def pretty_kwargs(self) -> StrMap:
297
- # return {
298
- # 'indent': PRETTY_INDENT,
299
- # }
300
- #
301
- # @cached.property
302
- # def compact_kwargs(self) -> StrMap:
303
- # return {
304
- # 'indent': 0,
305
- # 'separators': COMPACT_SEPARATORS,
306
- # }
307
- #
308
- #
309
- # def _select_provider(typs: ta.Iterable[ta.Callable[[], Provider]]) -> Provider:
310
- # for typ in typs:
311
- # try:
312
- # return typ()
313
- # except ImportError:
314
- # pass
315
- # raise TypeError('No suitable json providers')
316
- #
317
- #
318
- # PROVIDER: Provider = _select_provider([
319
- # OrjsonProvider,
320
- # UjsonProvider,
321
- # BuiltinProvider,
322
- # ])
323
- #
324
- #
325
- # dumps: Dumps = PROVIDER.dumps
326
- # dumpb: Dumpb = PROVIDER.dumpb
327
- # loads: Loads = PROVIDER.loads
328
- #
329
- # dumps_compact: Dumps = functools.partial(dumps, **PROVIDER.compact_kwargs)
330
- # dumpb_compact: Dumpb = functools.partial(dumpb, **PROVIDER.compact_kwargs)
331
- #
332
- # dumps_pretty: Dumps = functools.partial(dumps, **PROVIDER.pretty_kwargs)
333
- # dumpb_pretty: Dumpb = functools.partial(dumpb, **PROVIDER.pretty_kwargs)
@@ -0,0 +1,173 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import multiprocessing as mp
4
+ import multiprocessing.popen_spawn_posix
5
+ import os
6
+ import signal
7
+ import sys
8
+ import time
9
+ import typing as ta
10
+
11
+ from . import check
12
+ from . import libc
13
+
14
+
15
+ ##
16
+
17
+
18
+ @dc.dataclass(frozen=True, kw_only=True)
19
+ class SpawnExtras:
20
+ fds: ta.AbstractSet[int] | None = None
21
+ deathsig: int | None = None
22
+
23
+
24
+ class ExtrasSpawnPosixPopen(mp.popen_spawn_posix.Popen):
25
+ def __init__(self, process_obj: 'ExtrasSpawnProcess', *, extras: SpawnExtras) -> None:
26
+ self.__extras = extras
27
+ self.__extra_fds = extras.fds
28
+ super().__init__(process_obj)
29
+
30
+ def _launch(self, process_obj: 'ExtrasSpawnProcess') -> None:
31
+ if self.__extra_fds:
32
+ for fd in self.__extra_fds:
33
+ self.duplicate_for_child(fd)
34
+ self._extra_fds = None
35
+ super()._launch(process_obj) # type: ignore # noqa
36
+
37
+
38
+ class ExtrasSpawnProcess(mp.context.SpawnProcess):
39
+ def __init__(self, *args: ta.Any, extras: SpawnExtras, **kwargs: ta.Any) -> None:
40
+ self.__extras = extras
41
+ super().__init__(*args, **kwargs)
42
+
43
+ def _Popen(self, process_obj: 'ExtrasSpawnProcess') -> ExtrasSpawnPosixPopen: # type: ignore # noqa
44
+ return ExtrasSpawnPosixPopen(process_obj, extras=self.__extras)
45
+
46
+ def run(self) -> None:
47
+ if self.__extras.deathsig is not None and sys.platform == 'linux':
48
+ libc.prctl(libc.PR_SET_PDEATHSIG, self.__extras.deathsig, 0, 0, 0, 0)
49
+
50
+ super().run()
51
+
52
+
53
+ class ExtrasSpawnContext(mp.context.SpawnContext):
54
+ def __init__(self, extras: SpawnExtras = SpawnExtras()) -> None:
55
+ super().__init__()
56
+ self.__extras = extras
57
+
58
+ def Process(self, *args: ta.Any, **kwargs: ta.Any): # type: ignore # noqa
59
+ return ExtrasSpawnProcess(*args, extras=self.__extras, **kwargs)
60
+
61
+
62
+ ##
63
+
64
+
65
+ class Deathpact(abc.ABC):
66
+ @abc.abstractmethod
67
+ def poll(self) -> None:
68
+ raise NotImplementedError
69
+
70
+
71
+ #
72
+
73
+
74
+ class BaseDeathpact(Deathpact, abc.ABC):
75
+ def __init__(
76
+ self,
77
+ *,
78
+ interval_s: float = .5,
79
+ signal: int | None = signal.SIGTERM,
80
+ output: ta.Literal['stdout', 'stderr'] | None = 'stderr',
81
+ on_die: ta.Callable[[], None] | None = None,
82
+ ) -> None:
83
+ super().__init__()
84
+
85
+ self._interval_s = interval_s
86
+ self._signal = signal
87
+ self._output = output
88
+ self._on_die = on_die
89
+
90
+ self._last_check_t: float | None = None
91
+
92
+ def _print(self, msg: str) -> None:
93
+ match self._output:
94
+ case 'stdout':
95
+ f = sys.stdout
96
+ case 'stderr':
97
+ f = sys.stderr
98
+ case _:
99
+ return
100
+ print(f'{self} pid={os.getpid()}: {msg}', file=f)
101
+
102
+ def die(self) -> None:
103
+ self._print('Triggered! Process terminating!')
104
+
105
+ if self._on_die is not None:
106
+ self._on_die()
107
+
108
+ if self._signal is not None:
109
+ os.kill(os.getpid(), self._signal)
110
+
111
+ sys.exit(1)
112
+
113
+ @abc.abstractmethod
114
+ def should_die(self) -> bool:
115
+ raise NotImplementedError
116
+
117
+ def maybe_die(self) -> None:
118
+ if self.should_die():
119
+ self.die()
120
+
121
+ def poll(self) -> None:
122
+ if self._last_check_t is None or (time.monotonic() - self._last_check_t) >= self._interval_s:
123
+ self.maybe_die()
124
+ self._last_check_t = time.monotonic()
125
+
126
+
127
+ #
128
+
129
+
130
+ class PipeDeathpact(BaseDeathpact):
131
+ def __init__(self, **kwargs: ta.Any) -> None:
132
+ super().__init__(**kwargs)
133
+
134
+ self._rfd: int | None = None
135
+ self._wfd: int | None = None
136
+
137
+ def __repr__(self) -> str:
138
+ return f'{self.__class__.__name__}(rfd={self._rfd}, wfd={self._wfd})'
139
+
140
+ @property
141
+ def fd(self) -> int:
142
+ return check.not_none(self._rfd)
143
+
144
+ def __enter__(self) -> ta.Self:
145
+ check.none(self._rfd)
146
+ check.none(self._wfd)
147
+
148
+ self._rfd, self._wfd = os.pipe()
149
+
150
+ os.set_inheritable(self._rfd, True)
151
+ os.set_blocking(self._rfd, False)
152
+
153
+ return self
154
+
155
+ def __exit__(self, exc_type, exc_val, exc_tb):
156
+ if self._rfd is not None:
157
+ os.close(check.not_none(self._wfd))
158
+ self._wfd = None
159
+
160
+ os.close(self._rfd)
161
+ self._rfd = None
162
+
163
+ def should_die(self) -> bool:
164
+ try:
165
+ buf = os.read(check.not_none(self._rfd), 1)
166
+ except BlockingIOError:
167
+ return False
168
+
169
+ if buf:
170
+ self._print(f'Read data from pipe! This should not happen! Process state corrupt!')
171
+ self.die()
172
+
173
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev14
3
+ Version: 0.0.0.dev15
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -19,10 +19,10 @@ Requires-Dist: trio ~=0.26 ; extra == 'all'
19
19
  Requires-Dist: lz4 ~=4.0 ; extra == 'all'
20
20
  Requires-Dist: zstd ~=1.5 ; extra == 'all'
21
21
  Requires-Dist: psutil ~=6.0 ; extra == 'all'
22
- Requires-Dist: cloudpickle ~=3.0 ; extra == 'all'
22
+ Requires-Dist: orjson ~=3.10 ; extra == 'all'
23
23
  Requires-Dist: json5 ~=0.9 ; extra == 'all'
24
- Requires-Dist: orjson >3.10 ; extra == 'all'
25
24
  Requires-Dist: pyyaml ~=5.0 ; extra == 'all'
25
+ Requires-Dist: cloudpickle ~=3.0 ; extra == 'all'
26
26
  Requires-Dist: httpx[http2] ~=0.27 ; extra == 'all'
27
27
  Requires-Dist: jinja2 ~=3.1 ; extra == 'all'
28
28
  Requires-Dist: wrapt ~=1.14 ; extra == 'all'
@@ -53,10 +53,10 @@ Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'com
53
53
  Provides-Extra: diag
54
54
  Requires-Dist: psutil ~=6.0 ; extra == 'diag'
55
55
  Provides-Extra: formats
56
- Requires-Dist: cloudpickle ~=3.0 ; extra == 'formats'
56
+ Requires-Dist: orjson ~=3.10 ; extra == 'formats'
57
57
  Requires-Dist: json5 ~=0.9 ; extra == 'formats'
58
- Requires-Dist: orjson >3.10 ; extra == 'formats'
59
58
  Requires-Dist: pyyaml ~=5.0 ; extra == 'formats'
59
+ Requires-Dist: cloudpickle ~=3.0 ; extra == 'formats'
60
60
  Provides-Extra: http
61
61
  Requires-Dist: httpx[http2] ~=0.27 ; extra == 'http'
62
62
  Provides-Extra: misc
@@ -1,11 +1,11 @@
1
- omlish/__about__.py,sha256=2zVkeXvQ1CHLPrKoB44VisIZ0Pt3SPdWHxPCBgTXG8Q,2416
1
+ omlish/__about__.py,sha256=WLo5QZVhe2UU53contNJDxYZA3mKjiHL6lZTAr_rAvE,2493
2
2
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  omlish/argparse.py,sha256=QRQmX9G0-L_nATkFtGHvpd4qrpYzKATdjuFLbBqzJPM,6224
4
- omlish/bootstrap.py,sha256=3pGNiHlUcQl81q76_Wesd4pblmy82EOKkcdgectF15Q,18820
4
+ omlish/bootstrap.py,sha256=ENqzMlNbZWEAc2hzM9QwEoruShcgE_M7bdzJF6_fJ9w,19121
5
5
  omlish/c3.py,sha256=W5EwYx9Por3rWYLkKUitJ6OoRMLLgVTfLTyroOz41Y0,8047
6
6
  omlish/cached.py,sha256=UAizxlH4eMWHPzQtmItmyE6FEpFEUFzIkxaO2BHWZ5s,196
7
7
  omlish/check.py,sha256=o3UJnIEmmRsv9ggIIDtz8fDSudW1CatxbwxP42M4dno,5543
8
- omlish/concurrent.py,sha256=3KkTj5TOPmvNOcuK9LtOQwmkuLzoEYQfcuaHTXSQ0Ts,5424
8
+ omlish/concurrent.py,sha256=jCGG76Ca_8761g1uY3nCJONg_Nxcf4JQQZYvvrmsKE4,5395
9
9
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
10
10
  omlish/defs.py,sha256=N8O1murtr4mdSE_vjAcWGfnN7GyBXbW8rJM4qPF8na0,4737
11
11
  omlish/docker.py,sha256=5WyXJyFwqIJJ11QWwPIjHjDHnsaOVZszZAjyTvg3xew,4693
@@ -15,6 +15,7 @@ omlish/iterators.py,sha256=GGLC7RIT86uXMjhIIIqnff_Iu5SI_b9rXYywYGFyzmo,7292
15
15
  omlish/libc.py,sha256=u0481imCiTFqP_e-v9g0pD-0WD249j5vYzhtn-fnNkY,15308
16
16
  omlish/matchfns.py,sha256=o2evI7q0CAMHR8RQ_Jks6L0UoNpEDltnLjOiamJDtmU,6155
17
17
  omlish/math.py,sha256=AVqp5Y8yxKA-wO0BgrzaxA0Ga3PZiCXnYcwivMneC-0,3804
18
+ omlish/multiprocessing.py,sha256=6WI48mGUzLhDSwRjQs3ohxKWfl0v7wbxTjUvf_oOV4k,4584
18
19
  omlish/os.py,sha256=cz4nL2ujaxH_-XRq3JUD8af8mSe1JXGPIoXP9XAEd0M,2607
19
20
  omlish/runmodule.py,sha256=PWvuAaJ9wQQn6bx9ftEL3_d04DyotNn8dR_twm2pgw0,700
20
21
  omlish/stats.py,sha256=uqjN-focDVssFZMagj22HqmyJ1TBO4Wt-XnHp8-EtVw,9927
@@ -96,7 +97,7 @@ omlish/dispatch/functions.py,sha256=S8ElsLi6DKxTdtFGigWaF0vAquwy2sK-3f4iRLaYq70,
96
97
  omlish/dispatch/methods.py,sha256=XHjwwC9Gn4iDWxbyLAcbdSwRgVaq-8Bnn5cAwf5oZdA,5403
97
98
  omlish/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
99
  omlish/formats/dotenv.py,sha256=UjZl3gac-0U24sDjCCGMcCqO1UCWG2Zs8PZ4JdAg2YE,17348
99
- omlish/formats/json.py,sha256=KRfH-MLOrMXUFVy_eX6RagC37Z-KyfOIXlVdxM6nuxc,6787
100
+ omlish/formats/json.py,sha256=SgPpt-j0yLQDJaw_TkF471G2NrJPiTkhl8_Src81HGg,1016
100
101
  omlish/formats/props.py,sha256=diYjZDsG1s50ImJhkpeinMwjr8952nIVI-0gYhBIvCY,18897
101
102
  omlish/formats/yaml.py,sha256=5gvH-e9fVn6C-xpcagnrtM0bSLOsuk3sAQT-aaE4PD4,6653
102
103
  omlish/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -261,8 +262,8 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
261
262
  omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
262
263
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
263
264
  omlish/text/parts.py,sha256=KGgo0wHOIMVMZtDso-rhSWKAcAkYAH2IGpg9tULabu8,6505
264
- omlish-0.0.0.dev14.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
265
- omlish-0.0.0.dev14.dist-info/METADATA,sha256=ZEbsp-L49uyeqOh-kN3-Hda8oSu65aNEeucQXjLnCTw,3716
266
- omlish-0.0.0.dev14.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
267
- omlish-0.0.0.dev14.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
268
- omlish-0.0.0.dev14.dist-info/RECORD,,
265
+ omlish-0.0.0.dev15.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
266
+ omlish-0.0.0.dev15.dist-info/METADATA,sha256=IQXYzG4J9O7yaQvr5oHOVZ0piwijNGYK5LQ9TxhdzhM,3718
267
+ omlish-0.0.0.dev15.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
268
+ omlish-0.0.0.dev15.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
269
+ omlish-0.0.0.dev15.dist-info/RECORD,,