omdev 0.0.0.dev226__py3-none-any.whl → 0.0.0.dev228__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.
omdev/__about__.py CHANGED
@@ -13,7 +13,7 @@ class Project(ProjectBase):
13
13
 
14
14
  optional_dependencies = {
15
15
  'black': [
16
- 'black ~= 24.10',
16
+ 'black ~= 25.1',
17
17
  ],
18
18
 
19
19
  'c': [
@@ -46,7 +46,7 @@ class Project(ProjectBase):
46
46
  ],
47
47
 
48
48
  'wheel': [
49
- 'wheel ~= 0.44',
49
+ 'wheel ~= 0.45',
50
50
  ],
51
51
  }
52
52
 
omdev/ci/cache.py CHANGED
@@ -1,10 +1,17 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - os.mtime, Config.purge_after_days
5
+ - nice to have: get a total set of might-need keys ahead of time and keep those
6
+ - okay: just purge after running
7
+ """
2
8
  import abc
3
9
  import asyncio
4
10
  import dataclasses as dc
5
11
  import functools
6
12
  import os.path
7
13
  import shutil
14
+ import time
8
15
  import typing as ta
9
16
  import urllib.request
10
17
 
@@ -70,6 +77,11 @@ class DirectoryFileCache(FileCache):
70
77
  no_create: bool = False
71
78
  no_purge: bool = False
72
79
 
80
+ no_update_mtime: bool = False
81
+
82
+ purge_max_age_s: ta.Optional[float] = None
83
+ purge_max_size_b: ta.Optional[int] = None
84
+
73
85
  def __init__(
74
86
  self,
75
87
  config: Config,
@@ -90,6 +102,12 @@ class DirectoryFileCache(FileCache):
90
102
 
91
103
  VERSION_FILE_NAME = '.ci-cache-version'
92
104
 
105
+ def _iter_dir_contents(self) -> ta.Iterator[str]:
106
+ for n in sorted(os.listdir(self.dir)):
107
+ if n.startswith('.'):
108
+ continue
109
+ yield os.path.join(self.dir, n)
110
+
93
111
  @cached_nullary
94
112
  def setup_dir(self) -> None:
95
113
  version_file = os.path.join(self.dir, self.VERSION_FILE_NAME)
@@ -120,10 +138,7 @@ class DirectoryFileCache(FileCache):
120
138
  f'due to present directories: {", ".join(dirs)}',
121
139
  )
122
140
 
123
- for n in sorted(os.listdir(self.dir)):
124
- if n.startswith('.'):
125
- continue
126
- fp = os.path.join(self.dir, n)
141
+ for fp in self._iter_dir_contents():
127
142
  check.state(os.path.isfile(fp))
128
143
  log.debug('Purging stale cache file: %s', fp)
129
144
  os.unlink(fp)
@@ -135,6 +150,42 @@ class DirectoryFileCache(FileCache):
135
150
 
136
151
  #
137
152
 
153
+ def purge(self, *, dry_run: bool = False) -> None:
154
+ purge_max_age_s = self._config.purge_max_age_s
155
+ purge_max_size_b = self._config.purge_max_size_b
156
+ if self._config.no_purge or (purge_max_age_s is None and purge_max_size_b is None):
157
+ return
158
+
159
+ self.setup_dir()
160
+
161
+ purge_min_mtime: ta.Optional[float] = None
162
+ if purge_max_age_s is not None:
163
+ purge_min_mtime = time.time() - purge_max_age_s
164
+
165
+ dct: ta.Dict[str, os.stat_result] = {}
166
+ for fp in self._iter_dir_contents():
167
+ check.state(os.path.isfile(fp))
168
+ dct[fp] = os.stat(fp)
169
+
170
+ total_size_b = 0
171
+ for fp, st in sorted(dct.items(), key=lambda t: -t[1].st_mtime):
172
+ total_size_b += st.st_size
173
+
174
+ purge = False
175
+ if purge_min_mtime is not None and st.st_mtime < purge_min_mtime:
176
+ purge = True
177
+ if purge_max_size_b is not None and total_size_b >= purge_max_size_b:
178
+ purge = True
179
+
180
+ if not purge:
181
+ continue
182
+
183
+ log.debug('Purging cache file: %s', fp)
184
+ if not dry_run:
185
+ os.unlink(fp)
186
+
187
+ #
188
+
138
189
  def get_cache_file_path(
139
190
  self,
140
191
  key: str,
@@ -151,6 +202,11 @@ class DirectoryFileCache(FileCache):
151
202
  cache_file_path = self.get_cache_file_path(key)
152
203
  if not os.path.exists(cache_file_path):
153
204
  return None
205
+
206
+ if not self._config.no_update_mtime:
207
+ stat_info = os.stat(cache_file_path)
208
+ os.utime(cache_file_path, (stat_info.st_atime, time.time()))
209
+
154
210
  return cache_file_path
155
211
 
156
212
  async def put_file(
omdev/ci/cli.py CHANGED
@@ -25,6 +25,7 @@ from omlish.lite.inject import inj
25
25
  from omlish.lite.logs import log
26
26
  from omlish.logs.standard import configure_standard_logging
27
27
 
28
+ from .cache import DirectoryFileCache
28
29
  from .ci import Ci
29
30
  from .compose import get_compose_service_dependencies
30
31
  from .github.bootstrap import is_in_github_actions
@@ -70,6 +71,9 @@ class CiCli(ArgparseCli):
70
71
 
71
72
  #
72
73
 
74
+ DEFAULT_PURGE_MAX_AGE_S = 60 * 60 * 24 * 30
75
+ DEFAULT_PURGE_MAX_SIZE_B = 1024 * 1024 * 1024 * 4
76
+
73
77
  @argparse_cmd(
74
78
  argparse_arg('project-dir'),
75
79
  argparse_arg('service'),
@@ -79,6 +83,8 @@ class CiCli(ArgparseCli):
79
83
 
80
84
  argparse_arg('--cache-dir'),
81
85
 
86
+ argparse_arg('--no-purge', action='store_true'),
87
+
82
88
  argparse_arg('--github', action='store_true'),
83
89
  argparse_arg('--github-detect', action='store_true'),
84
90
 
@@ -202,17 +208,32 @@ class CiCli(ArgparseCli):
202
208
  run_options=run_options,
203
209
  )
204
210
 
211
+ directory_file_cache_config: ta.Optional[DirectoryFileCache.Config] = None
212
+ if cache_dir is not None:
213
+ directory_file_cache_config = DirectoryFileCache.Config(
214
+ dir=cache_dir,
215
+
216
+ no_purge=bool(self.args.no_purge),
217
+
218
+ purge_max_age_s=self.DEFAULT_PURGE_MAX_AGE_S,
219
+ purge_max_size_b=self.DEFAULT_PURGE_MAX_SIZE_B,
220
+ )
221
+
205
222
  injector = inj.create_injector(bind_ci(
206
223
  config=config,
207
224
 
208
- github=github,
225
+ directory_file_cache_config=directory_file_cache_config,
209
226
 
210
- cache_dir=cache_dir,
227
+ github=github,
211
228
  ))
212
229
 
213
230
  async with injector[Ci] as ci:
214
231
  await ci.run()
215
232
 
233
+ if directory_file_cache_config is not None and not directory_file_cache_config.no_purge:
234
+ dfc = injector[DirectoryFileCache]
235
+ dfc.purge()
236
+
216
237
 
217
238
  async def _async_main() -> ta.Optional[int]:
218
239
  return await CiCli().async_cli_run()
omdev/ci/github/cache.py CHANGED
@@ -21,14 +21,16 @@ from .client import GithubCacheServiceV1Client
21
21
  class GithubCache(FileCache, DataCache):
22
22
  @dc.dataclass(frozen=True)
23
23
  class Config:
24
- dir: str
24
+ pass
25
25
 
26
26
  def __init__(
27
27
  self,
28
- config: Config,
28
+ config: Config = Config(),
29
29
  *,
30
30
  client: ta.Optional[GithubCacheClient] = None,
31
31
  version: ta.Optional[CacheVersion] = None,
32
+
33
+ local: DirectoryFileCache,
32
34
  ) -> None:
33
35
  super().__init__(
34
36
  version=version,
@@ -42,12 +44,7 @@ class GithubCache(FileCache, DataCache):
42
44
  )
43
45
  self._client: GithubCacheClient = client
44
46
 
45
- self._local = DirectoryFileCache(
46
- DirectoryFileCache.Config(
47
- dir=check.non_empty_str(config.dir),
48
- ),
49
- version=self._version,
50
- )
47
+ self._local = local
51
48
 
52
49
  #
53
50
 
omdev/ci/github/inject.py CHANGED
@@ -12,19 +12,10 @@ from .cache import GithubCache
12
12
  ##
13
13
 
14
14
 
15
- def bind_github(
16
- *,
17
- cache_dir: ta.Optional[str] = None,
18
- ) -> InjectorBindings:
19
- lst: ta.List[InjectorBindingOrBindings] = []
20
-
21
- if cache_dir is not None:
22
- lst.extend([
23
- inj.bind(GithubCache.Config(
24
- dir=cache_dir,
25
- )),
26
- inj.bind(GithubCache, singleton=True),
27
- inj.bind(FileCache, to_key=GithubCache),
28
- ])
15
+ def bind_github() -> InjectorBindings:
16
+ lst: ta.List[InjectorBindingOrBindings] = [
17
+ inj.bind(GithubCache, singleton=True),
18
+ inj.bind(FileCache, to_key=GithubCache),
19
+ ]
29
20
 
30
21
  return inj.as_bindings(*lst)
omdev/ci/inject.py CHANGED
@@ -21,9 +21,9 @@ def bind_ci(
21
21
  *,
22
22
  config: Ci.Config,
23
23
 
24
- github: bool = False,
24
+ directory_file_cache_config: ta.Optional[DirectoryFileCache.Config] = None,
25
25
 
26
- cache_dir: ta.Optional[str] = None,
26
+ github: bool = False,
27
27
  ) -> InjectorBindings:
28
28
  lst: ta.List[InjectorBindingOrBindings] = [ # noqa
29
29
  inj.bind(config),
@@ -42,20 +42,15 @@ def bind_ci(
42
42
  ),
43
43
  ))
44
44
 
45
- if cache_dir is not None:
46
- if github:
47
- lst.append(bind_github(
48
- cache_dir=cache_dir,
49
- ))
45
+ if directory_file_cache_config is not None:
46
+ lst.extend([
47
+ inj.bind(directory_file_cache_config),
48
+ inj.bind(DirectoryFileCache, singleton=True),
49
+ ])
50
50
 
51
+ if github:
52
+ lst.append(bind_github())
51
53
  else:
52
- lst.extend([
53
- inj.bind(DirectoryFileCache.Config(
54
- dir=cache_dir,
55
- )),
56
- inj.bind(DirectoryFileCache, singleton=True),
57
- inj.bind(FileCache, to_key=DirectoryFileCache),
58
-
59
- ])
54
+ lst.append(inj.bind(FileCache, to_key=DirectoryFileCache))
60
55
 
61
56
  return inj.as_bindings(*lst)
omdev/scripts/ci.py CHANGED
@@ -24,6 +24,8 @@ import contextlib
24
24
  import contextvars
25
25
  import dataclasses as dc
26
26
  import datetime
27
+ import errno
28
+ import fcntl
27
29
  import functools
28
30
  import hashlib
29
31
  import http.client
@@ -1094,7 +1096,7 @@ class Timeout(abc.ABC):
1094
1096
 
1095
1097
  @classmethod
1096
1098
  def _now(cls) -> float:
1097
- return time.time()
1099
+ return time.monotonic()
1098
1100
 
1099
1101
  #
1100
1102
 
@@ -1417,20 +1419,33 @@ log_timing_context = LogTimingContext
1417
1419
  # ../../../omlish/os/files.py
1418
1420
 
1419
1421
 
1420
- def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
1422
+ def is_fd_open(fd: int) -> bool:
1423
+ try:
1424
+ fcntl.fcntl(fd, fcntl.F_GETFD)
1425
+ except OSError as e:
1426
+ if e.errno == errno.EBADF:
1427
+ return False
1428
+ raise
1429
+ else:
1430
+ return True
1431
+
1432
+
1433
+ def touch(path: str, mode: int = 0o666, exist_ok: bool = True) -> None:
1421
1434
  if exist_ok:
1422
1435
  # First try to bump modification time
1423
1436
  # Implementation note: GNU touch uses the UTIME_NOW option of the utimensat() / futimens() functions.
1424
1437
  try:
1425
- os.utime(self, None)
1438
+ os.utime(path, None)
1426
1439
  except OSError:
1427
1440
  pass
1428
1441
  else:
1429
1442
  return
1443
+
1430
1444
  flags = os.O_CREAT | os.O_WRONLY
1431
1445
  if not exist_ok:
1432
1446
  flags |= os.O_EXCL
1433
- fd = os.open(self, flags, mode)
1447
+
1448
+ fd = os.open(path, flags, mode)
1434
1449
  os.close(fd)
1435
1450
 
1436
1451
 
@@ -3458,6 +3473,12 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3458
3473
 
3459
3474
  ########################################
3460
3475
  # ../cache.py
3476
+ """
3477
+ TODO:
3478
+ - os.mtime, Config.purge_after_days
3479
+ - nice to have: get a total set of might-need keys ahead of time and keep those
3480
+ - okay: just purge after running
3481
+ """
3461
3482
 
3462
3483
 
3463
3484
  CacheVersion = ta.NewType('CacheVersion', int)
@@ -3514,6 +3535,11 @@ class DirectoryFileCache(FileCache):
3514
3535
  no_create: bool = False
3515
3536
  no_purge: bool = False
3516
3537
 
3538
+ no_update_mtime: bool = False
3539
+
3540
+ purge_max_age_s: ta.Optional[float] = None
3541
+ purge_max_size_b: ta.Optional[int] = None
3542
+
3517
3543
  def __init__(
3518
3544
  self,
3519
3545
  config: Config,
@@ -3534,6 +3560,12 @@ class DirectoryFileCache(FileCache):
3534
3560
 
3535
3561
  VERSION_FILE_NAME = '.ci-cache-version'
3536
3562
 
3563
+ def _iter_dir_contents(self) -> ta.Iterator[str]:
3564
+ for n in sorted(os.listdir(self.dir)):
3565
+ if n.startswith('.'):
3566
+ continue
3567
+ yield os.path.join(self.dir, n)
3568
+
3537
3569
  @cached_nullary
3538
3570
  def setup_dir(self) -> None:
3539
3571
  version_file = os.path.join(self.dir, self.VERSION_FILE_NAME)
@@ -3564,10 +3596,7 @@ class DirectoryFileCache(FileCache):
3564
3596
  f'due to present directories: {", ".join(dirs)}',
3565
3597
  )
3566
3598
 
3567
- for n in sorted(os.listdir(self.dir)):
3568
- if n.startswith('.'):
3569
- continue
3570
- fp = os.path.join(self.dir, n)
3599
+ for fp in self._iter_dir_contents():
3571
3600
  check.state(os.path.isfile(fp))
3572
3601
  log.debug('Purging stale cache file: %s', fp)
3573
3602
  os.unlink(fp)
@@ -3579,6 +3608,42 @@ class DirectoryFileCache(FileCache):
3579
3608
 
3580
3609
  #
3581
3610
 
3611
+ def purge(self, *, dry_run: bool = False) -> None:
3612
+ purge_max_age_s = self._config.purge_max_age_s
3613
+ purge_max_size_b = self._config.purge_max_size_b
3614
+ if self._config.no_purge or (purge_max_age_s is None and purge_max_size_b is None):
3615
+ return
3616
+
3617
+ self.setup_dir()
3618
+
3619
+ purge_min_mtime: ta.Optional[float] = None
3620
+ if purge_max_age_s is not None:
3621
+ purge_min_mtime = time.time() - purge_max_age_s
3622
+
3623
+ dct: ta.Dict[str, os.stat_result] = {}
3624
+ for fp in self._iter_dir_contents():
3625
+ check.state(os.path.isfile(fp))
3626
+ dct[fp] = os.stat(fp)
3627
+
3628
+ total_size_b = 0
3629
+ for fp, st in sorted(dct.items(), key=lambda t: -t[1].st_mtime):
3630
+ total_size_b += st.st_size
3631
+
3632
+ purge = False
3633
+ if purge_min_mtime is not None and st.st_mtime < purge_min_mtime:
3634
+ purge = True
3635
+ if purge_max_size_b is not None and total_size_b >= purge_max_size_b:
3636
+ purge = True
3637
+
3638
+ if not purge:
3639
+ continue
3640
+
3641
+ log.debug('Purging cache file: %s', fp)
3642
+ if not dry_run:
3643
+ os.unlink(fp)
3644
+
3645
+ #
3646
+
3582
3647
  def get_cache_file_path(
3583
3648
  self,
3584
3649
  key: str,
@@ -3595,6 +3660,11 @@ class DirectoryFileCache(FileCache):
3595
3660
  cache_file_path = self.get_cache_file_path(key)
3596
3661
  if not os.path.exists(cache_file_path):
3597
3662
  return None
3663
+
3664
+ if not self._config.no_update_mtime:
3665
+ stat_info = os.stat(cache_file_path)
3666
+ os.utime(cache_file_path, (stat_info.st_atime, time.time()))
3667
+
3598
3668
  return cache_file_path
3599
3669
 
3600
3670
  async def put_file(
@@ -4352,14 +4422,16 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
4352
4422
  class GithubCache(FileCache, DataCache):
4353
4423
  @dc.dataclass(frozen=True)
4354
4424
  class Config:
4355
- dir: str
4425
+ pass
4356
4426
 
4357
4427
  def __init__(
4358
4428
  self,
4359
- config: Config,
4429
+ config: Config = Config(),
4360
4430
  *,
4361
4431
  client: ta.Optional[GithubCacheClient] = None,
4362
4432
  version: ta.Optional[CacheVersion] = None,
4433
+
4434
+ local: DirectoryFileCache,
4363
4435
  ) -> None:
4364
4436
  super().__init__(
4365
4437
  version=version,
@@ -4373,12 +4445,7 @@ class GithubCache(FileCache, DataCache):
4373
4445
  )
4374
4446
  self._client: GithubCacheClient = client
4375
4447
 
4376
- self._local = DirectoryFileCache(
4377
- DirectoryFileCache.Config(
4378
- dir=check.non_empty_str(config.dir),
4379
- ),
4380
- version=self._version,
4381
- )
4448
+ self._local = local
4382
4449
 
4383
4450
  #
4384
4451
 
@@ -4677,20 +4744,11 @@ class BaseSubprocesses(abc.ABC): # noqa
4677
4744
  ##
4678
4745
 
4679
4746
 
4680
- def bind_github(
4681
- *,
4682
- cache_dir: ta.Optional[str] = None,
4683
- ) -> InjectorBindings:
4684
- lst: ta.List[InjectorBindingOrBindings] = []
4685
-
4686
- if cache_dir is not None:
4687
- lst.extend([
4688
- inj.bind(GithubCache.Config(
4689
- dir=cache_dir,
4690
- )),
4691
- inj.bind(GithubCache, singleton=True),
4692
- inj.bind(FileCache, to_key=GithubCache),
4693
- ])
4747
+ def bind_github() -> InjectorBindings:
4748
+ lst: ta.List[InjectorBindingOrBindings] = [
4749
+ inj.bind(GithubCache, singleton=True),
4750
+ inj.bind(FileCache, to_key=GithubCache),
4751
+ ]
4694
4752
 
4695
4753
  return inj.as_bindings(*lst)
4696
4754
 
@@ -5912,9 +5970,9 @@ def bind_ci(
5912
5970
  *,
5913
5971
  config: Ci.Config,
5914
5972
 
5915
- github: bool = False,
5973
+ directory_file_cache_config: ta.Optional[DirectoryFileCache.Config] = None,
5916
5974
 
5917
- cache_dir: ta.Optional[str] = None,
5975
+ github: bool = False,
5918
5976
  ) -> InjectorBindings:
5919
5977
  lst: ta.List[InjectorBindingOrBindings] = [ # noqa
5920
5978
  inj.bind(config),
@@ -5933,21 +5991,16 @@ def bind_ci(
5933
5991
  ),
5934
5992
  ))
5935
5993
 
5936
- if cache_dir is not None:
5937
- if github:
5938
- lst.append(bind_github(
5939
- cache_dir=cache_dir,
5940
- ))
5994
+ if directory_file_cache_config is not None:
5995
+ lst.extend([
5996
+ inj.bind(directory_file_cache_config),
5997
+ inj.bind(DirectoryFileCache, singleton=True),
5998
+ ])
5941
5999
 
6000
+ if github:
6001
+ lst.append(bind_github())
5942
6002
  else:
5943
- lst.extend([
5944
- inj.bind(DirectoryFileCache.Config(
5945
- dir=cache_dir,
5946
- )),
5947
- inj.bind(DirectoryFileCache, singleton=True),
5948
- inj.bind(FileCache, to_key=DirectoryFileCache),
5949
-
5950
- ])
6003
+ lst.append(inj.bind(FileCache, to_key=DirectoryFileCache))
5951
6004
 
5952
6005
  return inj.as_bindings(*lst)
5953
6006
 
@@ -5992,6 +6045,9 @@ class CiCli(ArgparseCli):
5992
6045
 
5993
6046
  #
5994
6047
 
6048
+ DEFAULT_PURGE_MAX_AGE_S = 60 * 60 * 24 * 30
6049
+ DEFAULT_PURGE_MAX_SIZE_B = 1024 * 1024 * 1024 * 4
6050
+
5995
6051
  @argparse_cmd(
5996
6052
  argparse_arg('project-dir'),
5997
6053
  argparse_arg('service'),
@@ -6001,6 +6057,8 @@ class CiCli(ArgparseCli):
6001
6057
 
6002
6058
  argparse_arg('--cache-dir'),
6003
6059
 
6060
+ argparse_arg('--no-purge', action='store_true'),
6061
+
6004
6062
  argparse_arg('--github', action='store_true'),
6005
6063
  argparse_arg('--github-detect', action='store_true'),
6006
6064
 
@@ -6124,17 +6182,32 @@ class CiCli(ArgparseCli):
6124
6182
  run_options=run_options,
6125
6183
  )
6126
6184
 
6185
+ directory_file_cache_config: ta.Optional[DirectoryFileCache.Config] = None
6186
+ if cache_dir is not None:
6187
+ directory_file_cache_config = DirectoryFileCache.Config(
6188
+ dir=cache_dir,
6189
+
6190
+ no_purge=bool(self.args.no_purge),
6191
+
6192
+ purge_max_age_s=self.DEFAULT_PURGE_MAX_AGE_S,
6193
+ purge_max_size_b=self.DEFAULT_PURGE_MAX_SIZE_B,
6194
+ )
6195
+
6127
6196
  injector = inj.create_injector(bind_ci(
6128
6197
  config=config,
6129
6198
 
6130
- github=github,
6199
+ directory_file_cache_config=directory_file_cache_config,
6131
6200
 
6132
- cache_dir=cache_dir,
6201
+ github=github,
6133
6202
  ))
6134
6203
 
6135
6204
  async with injector[Ci] as ci:
6136
6205
  await ci.run()
6137
6206
 
6207
+ if directory_file_cache_config is not None and not directory_file_cache_config.no_purge:
6208
+ dfc = injector[DirectoryFileCache]
6209
+ dfc.purge()
6210
+
6138
6211
 
6139
6212
  async def _async_main() -> ta.Optional[int]:
6140
6213
  return await CiCli().async_cli_run()
omdev/scripts/interp.py CHANGED
@@ -1340,7 +1340,7 @@ class Timeout(abc.ABC):
1340
1340
 
1341
1341
  @classmethod
1342
1342
  def _now(cls) -> float:
1343
- return time.time()
1343
+ return time.monotonic()
1344
1344
 
1345
1345
  #
1346
1346
 
@@ -2648,7 +2648,7 @@ class Timeout(abc.ABC):
2648
2648
 
2649
2649
  @classmethod
2650
2650
  def _now(cls) -> float:
2651
- return time.time()
2651
+ return time.monotonic()
2652
2652
 
2653
2653
  #
2654
2654
 
omdev/tagstrings.py CHANGED
@@ -21,7 +21,10 @@ TAG_STRING_VALUE_TYPES: ta.Tuple = (
21
21
  )
22
22
 
23
23
 
24
- TAG_STRING_BOOL_STR_MAP: ta.Mapping[str, bool] = {'true': True, 'false': False}
24
+ TAG_STRING_BOOL_STR_MAP: ta.Mapping[str, bool] = {
25
+ 'true': True,
26
+ 'false': False,
27
+ }
25
28
 
26
29
 
27
30
  def check_tag_string_string(s: str) -> str:
@@ -31,7 +34,9 @@ def check_tag_string_string(s: str) -> str:
31
34
  return s
32
35
 
33
36
 
34
- def build_hierarchy_tag_string_values(m: ta.Mapping[str, ta.Any]) -> ta.FrozenSet[HierarchyTagStringValue]:
37
+ def build_hierarchy_tag_string_values(
38
+ m: ta.Mapping[str, ta.Any],
39
+ ) -> ta.FrozenSet[HierarchyTagStringValue]:
35
40
  def rec(c):
36
41
  if isinstance(c, str):
37
42
  yield (c,)
@@ -98,7 +103,10 @@ class TagString(ta.Generic[TagStringValueT]):
98
103
  return cls(
99
104
  name=name,
100
105
  type=bool, # type: ignore
101
- valid_values=frozenset(valid_values) if valid_values is not None else None, # type: ignore
106
+ valid_values=(
107
+ frozenset(valid_values) # type: ignore
108
+ if valid_values is not None else None
109
+ ),
102
110
  **kwargs,
103
111
  )
104
112
 
@@ -112,7 +120,10 @@ class TagString(ta.Generic[TagStringValueT]):
112
120
  return cls(
113
121
  name=name,
114
122
  type=str, # type: ignore
115
- valid_values=frozenset(check.not_isinstance(valid_values, str)) if valid_values is not None else None, # type: ignore # noqa
123
+ valid_values=(
124
+ frozenset(check.not_isinstance(valid_values, str)) # type: ignore
125
+ if valid_values is not None else None
126
+ ),
116
127
  **kwargs,
117
128
  )
118
129
 
@@ -126,7 +137,10 @@ class TagString(ta.Generic[TagStringValueT]):
126
137
  return cls(
127
138
  name=name,
128
139
  type=HierarchyTagStringValue, # type: ignore
129
- valid_values=build_hierarchy_tag_string_values(valid_values) if valid_values is not None else None, # type: ignore # noqa
140
+ valid_values=(
141
+ build_hierarchy_tag_string_values(valid_values) # type: ignore
142
+ if valid_values is not None else None
143
+ ),
130
144
  **kwargs,
131
145
  )
132
146
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omdev
3
- Version: 0.0.0.dev226
3
+ Version: 0.0.0.dev228
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,9 +12,9 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev226
15
+ Requires-Dist: omlish==0.0.0.dev228
16
16
  Provides-Extra: all
17
- Requires-Dist: black~=24.10; extra == "all"
17
+ Requires-Dist: black~=25.1; extra == "all"
18
18
  Requires-Dist: pycparser~=2.22; extra == "all"
19
19
  Requires-Dist: pcpp~=1.30; extra == "all"
20
20
  Requires-Dist: cffi~=1.17; extra == "all"
@@ -24,9 +24,9 @@ Requires-Dist: mypy~=1.11; extra == "all"
24
24
  Requires-Dist: gprof2dot~=2024.6; extra == "all"
25
25
  Requires-Dist: prompt-toolkit~=3.0; extra == "all"
26
26
  Requires-Dist: segno~=1.6; extra == "all"
27
- Requires-Dist: wheel~=0.44; extra == "all"
27
+ Requires-Dist: wheel~=0.45; extra == "all"
28
28
  Provides-Extra: black
29
- Requires-Dist: black~=24.10; extra == "black"
29
+ Requires-Dist: black~=25.1; extra == "black"
30
30
  Provides-Extra: c
31
31
  Requires-Dist: pycparser~=2.22; extra == "c"
32
32
  Requires-Dist: pcpp~=1.30; extra == "c"
@@ -43,4 +43,4 @@ Requires-Dist: prompt-toolkit~=3.0; extra == "ptk"
43
43
  Provides-Extra: qr
44
44
  Requires-Dist: segno~=1.6; extra == "qr"
45
45
  Provides-Extra: wheel
46
- Requires-Dist: wheel~=0.44; extra == "wheel"
46
+ Requires-Dist: wheel~=0.45; extra == "wheel"
@@ -1,5 +1,5 @@
1
1
  omdev/.manifests.json,sha256=02pFpcoefn9JQr0AIqt_6-BnWi49KF0baoGEKv8bjn0,9093
2
- omdev/__about__.py,sha256=j3vFclhFvyPICV6FK4aDApFzMCqJWxv9FaWwdwXrSgw,1215
2
+ omdev/__about__.py,sha256=Iect_SBD2EXgx7QcFGiOqTHkOWD-bWOyvzgReDOY4Es,1214
3
3
  omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omdev/bracepy.py,sha256=I8EdqtDvxzAi3I8TuMEW-RBfwXfqKbwp06CfOdj3L1o,2743
5
5
  omdev/classdot.py,sha256=YOvgy6x295I_8NKBbBlRVd3AN7Osirm_Lqt4Wj0j9rY,1631
@@ -9,7 +9,7 @@ omdev/imgur.py,sha256=h38w1a1hFYAysfmD4uYXZo1yMj0NKzWgdQmedVSHnTc,3000
9
9
  omdev/pip.py,sha256=7cZ_IOpekQvgPm_gKnX3Pr8xjqUid50PPScTlZCYVlM,2118
10
10
  omdev/revisions.py,sha256=0feRWC0Uttd6K9cCImAHEXoo6-Nuso3tpaHUuhzBlRQ,4985
11
11
  omdev/secrets.py,sha256=aC1o2vJtdLpa_MJoO2P2wty1pfqgAPytj54CamLLFWw,544
12
- omdev/tagstrings.py,sha256=hrinoRmYCFMt4WYCZAYrocVYKQvIApNGKbJaGz8whqs,5334
12
+ omdev/tagstrings.py,sha256=zIP7nzcsZf5te0lphu6k36ND_cOvNFRg00neoTcoCs8,5484
13
13
  omdev/wheelfile.py,sha256=yfupGcGkbFlmzGzKU64k_vmOKpaKnUlDWxeGn2KdekU,10005
14
14
  omdev/amalg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  omdev/amalg/__main__.py,sha256=1sZH8SLAueWxMxK9ngvndUW3L_rw7f-s_jK3ZP1yAH8,170
@@ -71,12 +71,12 @@ omdev/cexts/_distutils/compilers/options.py,sha256=H7r5IcLvga5Fs3jjXWIT-6ap3JBdu
71
71
  omdev/cexts/_distutils/compilers/unixccompiler.py,sha256=o1h8QuyupLntv4F21_XjzAZmCiwwxJuTmOirvBSL-Qw,15419
72
72
  omdev/ci/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
73
73
  omdev/ci/__main__.py,sha256=Jsrv3P7LX2Cg08W7ByZfZ1JQT4lgLDPW1qNAmShFuMk,75
74
- omdev/ci/cache.py,sha256=ohgWIbefNNbqwFLzpdDzqWabogwXZugfJ2AL64U3xsw,6578
74
+ omdev/ci/cache.py,sha256=K3SirapVPEGcdq2_G1DeLc0hxweTPeer9DH3LlZZK8w,8350
75
75
  omdev/ci/ci.py,sha256=JNFDs3sYCs93NOrnQxKiZNVnOotOwOL1CIEB4TL--Fg,6342
76
- omdev/ci/cli.py,sha256=URmYwsvNSb4w_kwluLB4Em4ooTpiLG4Bh8Anjx0IVIE,6013
76
+ omdev/ci/cli.py,sha256=CgFxNllLCn-hgVsq3w_RCBb3j3jjacZVtkFNQXxgBmE,6828
77
77
  omdev/ci/compose.py,sha256=vHLuXO5e2paafBC0Kf-OUGoamtIJmQ19r2U3_oikk_g,4541
78
78
  omdev/ci/consts.py,sha256=1puYfksvGOaVWEnbARM_sdMqs8oTn_VvsevsOtLsFno,21
79
- omdev/ci/inject.py,sha256=ZZWaT_GT4SE_lO8st2Y2IjKmeAtL3LD7KjEVr-E6Zzo,1514
79
+ omdev/ci/inject.py,sha256=xo3AkobJKLLE7ZTa084NeGZm8pCABh4Wmh-RLRHFjTs,1460
80
80
  omdev/ci/requirements.py,sha256=UKN6avU5V96YmmJL8XYvMPxzzOagrKpGTYadaxI2z9U,2105
81
81
  omdev/ci/shell.py,sha256=cBPLMKiAJuNpPGj3ou6hpl88Xw7r99xpL91KJBQ0rqw,835
82
82
  omdev/ci/utils.py,sha256=YxOT4S-YLDOAv27K0Q0SKzxncZrWFA_wNXlFOaJmQuI,304
@@ -94,11 +94,11 @@ omdev/ci/docker/utils.py,sha256=URioGRzqyqdJBZyOfzsrUwv5hSJ3WM23_sLHES9vamc,1129
94
94
  omdev/ci/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
95
  omdev/ci/github/api.py,sha256=Vqza7Hm1OCSfZYgdXF4exkjneqNjFcdO1pl8qmODskU,5198
96
96
  omdev/ci/github/bootstrap.py,sha256=9OuftAz7CUd7uf2Or3sJFVozQQiwu0RGAlTOQNpLQIY,430
97
- omdev/ci/github/cache.py,sha256=39n0H4KpRCryQeIGVJqL6coyg7q4IFpFZZyBkbOo6PM,2672
97
+ omdev/ci/github/cache.py,sha256=n0nuEaGidvXBfB1ZU1G2Khp5Wuztoh_uNjPkny8KDdQ,2553
98
98
  omdev/ci/github/cli.py,sha256=6mG0CllwrOoC7MDzKfKDqBHAjfF0gEI6aT5UAGMmuss,1114
99
99
  omdev/ci/github/client.py,sha256=fT8rQ5RO5MXyjpIt6UEFR7escofkBau73m8KYMzcZFo,14614
100
100
  omdev/ci/github/env.py,sha256=FQFjP_m7JWM7es9I51U-6UgJTwAt_UCVHFIYKTd9NKM,394
101
- omdev/ci/github/inject.py,sha256=FnmAL9vcZSYKvbuNjYDYlrc-XiFPayR806pYUd4Dyao,689
101
+ omdev/ci/github/inject.py,sha256=Dwm2-qujyIqKYpBhiuebV3Lg5Gzf_cqk13NFVbf-8PQ,479
102
102
  omdev/cli/__init__.py,sha256=V_l6VP1SZMlJbO-8CJwSuO9TThOy2S_oaPepNYgIrbE,37
103
103
  omdev/cli/__main__.py,sha256=mOJpgc07o0r5luQ1DlX4tk2PqZkgmbwPbdzJ3KmtjgQ,138
104
104
  omdev/cli/_pathhack.py,sha256=kxqb2kHap68Lkh8b211rDbcgj06hidBiAKA3f9posyc,2119
@@ -206,12 +206,12 @@ omdev/pyproject/resources/docker-dev.sh,sha256=DHkz5D18jok_oDolfg2mqrvGRWFoCe9GQ
206
206
  omdev/pyproject/resources/python.sh,sha256=rFaN4SiJ9hdLDXXsDTwugI6zsw6EPkgYMmtacZeTbvw,749
207
207
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
208
208
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
209
- omdev/scripts/ci.py,sha256=IZYSToZ9UyNZKiyOnbrk7pfuJhlyamMLmkQX6nwYKP8,159949
209
+ omdev/scripts/ci.py,sha256=sZeKGLX3XsmhL7n02npohUpE3Z3MWe03AkhZAh5mU94,162353
210
210
  omdev/scripts/execrss.py,sha256=mR0G0wERBYtQmVIn63lCIIFb5zkCM6X_XOENDFYDBKc,651
211
211
  omdev/scripts/exectime.py,sha256=S2O4MgtzTsFOY2IUJxsrnOIame9tEFc6aOlKP-F1JSg,1541
212
212
  omdev/scripts/importtrace.py,sha256=oa7CtcWJVMNDbyIEiRHej6ICfABfErMeo4_haIqe18Q,14041
213
- omdev/scripts/interp.py,sha256=AYxF2dftxs1anS211-qWmJKzw_B_HIbZIyP6OC5eFdY,150405
214
- omdev/scripts/pyproject.py,sha256=D8GgeTEOC8pKEwUX8whjOPuMGsYGRRKA8nIzXOAli_Y,258078
213
+ omdev/scripts/interp.py,sha256=hKBiphk6k9g2QVkPbEYDmdAeCQCDmhv98u5qfvAFlcA,150410
214
+ omdev/scripts/pyproject.py,sha256=qC1HifBvYyDOj_Caw6B7AvklgYcdtLhejVtA6V2yDYk,258083
215
215
  omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
216
216
  omdev/scripts/tmpexec.py,sha256=WTYcf56Tj2qjYV14AWmV8SfT0u6Y8eIU6cKgQRvEK3c,1442
217
217
  omdev/tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -243,9 +243,9 @@ omdev/tools/json/rendering.py,sha256=tMcjOW5edfozcMSTxxvF7WVTsbYLoe9bCKFh50qyaGw
243
243
  omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
244
  omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
245
245
  omdev/tools/pawk/pawk.py,sha256=zsEkfQX0jF5bn712uqPAyBSdJt2dno1LH2oeSMNfXQI,11424
246
- omdev-0.0.0.dev226.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
247
- omdev-0.0.0.dev226.dist-info/METADATA,sha256=XK1jVJw9D5_tEULYgkiKcbQYJBjQJE0QVddRh7NsU4c,1638
248
- omdev-0.0.0.dev226.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
249
- omdev-0.0.0.dev226.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
250
- omdev-0.0.0.dev226.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
251
- omdev-0.0.0.dev226.dist-info/RECORD,,
246
+ omdev-0.0.0.dev228.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
247
+ omdev-0.0.0.dev228.dist-info/METADATA,sha256=F21kNK_t2VCTGzAd_c0dW2mXJ1zKvOPizv8l2cwnV4s,1636
248
+ omdev-0.0.0.dev228.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
249
+ omdev-0.0.0.dev228.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
250
+ omdev-0.0.0.dev228.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
251
+ omdev-0.0.0.dev228.dist-info/RECORD,,