omlish 0.0.0.dev243__py3-none-any.whl → 0.0.0.dev245__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.dev243'
2
- __revision__ = '97467f9facc16b72c3dac69aba18c543dce228b1'
1
+ __version__ = '0.0.0.dev245'
2
+ __revision__ = 'c3979ac75a1b25b3c679bc0310afb6364c124c4c'
3
3
 
4
4
 
5
5
  #
omlish/cached.py CHANGED
@@ -9,11 +9,12 @@ builtins and thus not distinguish it from a normal property.
9
9
  def p(self) -> str: ...
10
10
 
11
11
  """
12
- from .lang.cached import _CachedProperty # noqa
13
- from .lang.cached import cached_function as _cached_function
12
+ from .lang.cached.function import cached_function as _cached_function
13
+ from .lang.cached.property import cached_property as _cached_property
14
+
14
15
 
15
16
  function = _cached_function
16
17
 
17
18
  property = property # noqa
18
19
 
19
- globals()['property'] = _CachedProperty # noqa
20
+ globals()['property'] = _cached_property # noqa
omlish/daemons/daemon.py CHANGED
@@ -48,7 +48,9 @@ class Daemon:
48
48
 
49
49
  #
50
50
 
51
+ # TODO: None, defaults, figure out from spawn method
51
52
  reparent_process: bool = False
53
+
52
54
  launched_timeout_s: float = 5.
53
55
 
54
56
  #
@@ -126,7 +128,7 @@ class Daemon:
126
128
 
127
129
  #
128
130
 
129
- def launch_no_wait(self) -> None:
131
+ def launch_no_wait(self) -> bool:
130
132
  launcher = Launcher(
131
133
  target=self._target,
132
134
  spawning=check.not_none(self._config.spawning),
@@ -136,7 +138,7 @@ class Daemon:
136
138
  launched_timeout_s=self._config.launched_timeout_s,
137
139
  )
138
140
 
139
- launcher.launch()
141
+ return launcher.launch()
140
142
 
141
143
  def launch(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
142
144
  self.launch_no_wait()
@@ -53,8 +53,10 @@ class Launcher:
53
53
  self,
54
54
  *,
55
55
  pidfile_manager: ta.ContextManager | None,
56
- launched_callback: ta.Callable[[], None] | None = None,
56
+ callback: ta.Callable[[], None] | None = None,
57
57
  ) -> None:
58
+ callback_called = False
59
+
58
60
  try:
59
61
  if self._reparent_process:
60
62
  log.info('Reparenting')
@@ -66,15 +68,16 @@ class Launcher:
66
68
  pidfile = check.isinstance(es.enter_context(pidfile_manager), Pidfile)
67
69
  pidfile.write()
68
70
 
69
- if launched_callback is not None:
70
- launched_callback()
71
+ if callback is not None:
72
+ callback_called = True
73
+ callback()
71
74
 
72
75
  runner = target_runner_for(self._target)
73
76
  runner.run()
74
77
 
75
78
  finally:
76
- if launched_callback is not None:
77
- launched_callback()
79
+ if callback is not None and not callback_called:
80
+ callback()
78
81
 
79
82
  def launch(self) -> bool:
80
83
  with contextlib.ExitStack() as es:
@@ -110,8 +113,9 @@ class Launcher:
110
113
  functools.partial(
111
114
  self._inner_launch,
112
115
  pidfile_manager=pidfile_manager,
113
- launched_callback=launched_event.set if launched_event is not None else None,
116
+ callback=launched_event.set if launched_event is not None else None,
114
117
  ),
118
+ target=self._target,
115
119
  inherit_fds=inherit_fds,
116
120
  ))
117
121
 
@@ -2,7 +2,10 @@ import os
2
2
  import sys
3
3
 
4
4
 
5
- def reparent_process() -> None:
5
+ def reparent_process(
6
+ *,
7
+ no_close_stdio: bool = False,
8
+ ) -> None:
6
9
  if (pid := os.fork()): # noqa
7
10
  sys.exit(0)
8
11
  raise RuntimeError('Unreachable') # noqa
@@ -12,5 +15,15 @@ def reparent_process() -> None:
12
15
  if (pid := os.fork()): # noqa
13
16
  sys.exit(0)
14
17
 
18
+ if not no_close_stdio:
19
+ rn_fd = os.open('/dev/null', os.O_RDONLY)
20
+ os.dup2(rn_fd, 0)
21
+ os.close(rn_fd)
22
+
23
+ wn_fd = os.open('/dev/null', os.O_WRONLY)
24
+ os.dup2(wn_fd, 1)
25
+ os.dup2(wn_fd, 2)
26
+ os.close(wn_fd)
27
+
15
28
  sys.stdout.flush()
16
29
  sys.stderr.flush()
@@ -1,4 +1,5 @@
1
1
  import abc
2
+ import threading
2
3
  import typing as ta
3
4
 
4
5
  from .. import cached
@@ -12,6 +13,7 @@ from .targets import TargetRunner
12
13
  from .targets import target_runner_for
13
14
 
14
15
 
16
+ ServiceT = ta.TypeVar('ServiceT', bound='Service')
15
17
  ServiceConfigT = ta.TypeVar('ServiceConfigT', bound='Service.Config')
16
18
 
17
19
 
@@ -82,27 +84,53 @@ def _(target: ServiceConfigTarget) -> ServiceConfigTargetRunner:
82
84
 
83
85
 
84
86
  @dc.dataclass(frozen=True)
85
- class ServiceDaemon(lang.Final):
86
- service: Service | Service.Config
87
+ class ServiceDaemon(lang.Final, ta.Generic[ServiceT, ServiceConfigT]):
88
+ service: ServiceT | ServiceConfigT
87
89
 
88
90
  @cached.function
89
- def service_(self) -> Service:
90
- if isinstance(self.service, Service):
91
- return self.service
92
- elif isinstance(self.service, Service.Config):
93
- return Service.from_config(self.service)
94
- else:
95
- raise TypeError(self.service)
91
+ def service_config(self) -> ServiceConfigT:
92
+ with self._lock:
93
+ if isinstance(self.service, Service):
94
+ return self.service.config
95
+ elif isinstance(self.service, Service.Config):
96
+ return self.service
97
+ else:
98
+ raise TypeError(self.service)
99
+
100
+ @cached.function
101
+ def service_(self) -> ServiceT:
102
+ with self._lock:
103
+ if isinstance(self.service, Service):
104
+ return self.service # type: ignore[return-value]
105
+ elif isinstance(self.service, Service.Config):
106
+ return Service.from_config(self.service) # type: ignore[return-value]
107
+ else:
108
+ raise TypeError(self.service)
96
109
 
97
110
  #
98
111
 
99
112
  daemon: Daemon | Daemon.Config = Daemon.Config()
100
113
 
114
+ @cached.function
115
+ def daemon_config(self) -> Daemon.Config:
116
+ with self._lock:
117
+ if isinstance(self.daemon, Daemon):
118
+ return self.daemon.config
119
+ elif isinstance(self.daemon, Daemon.Config):
120
+ return self.daemon
121
+ else:
122
+ raise TypeError(self.daemon)
123
+
101
124
  @cached.function
102
125
  def daemon_(self) -> Daemon:
103
- if isinstance(self.daemon, Daemon):
104
- return self.daemon
105
- elif isinstance(self.daemon, Daemon.Config):
106
- return Daemon(Target.of(self.service_()), self.daemon)
107
- else:
108
- raise TypeError(self.daemon)
126
+ with self._lock:
127
+ if isinstance(self.daemon, Daemon):
128
+ return self.daemon
129
+ elif isinstance(self.daemon, Daemon.Config):
130
+ return Daemon(Target.of(self.service_()), self.daemon)
131
+ else:
132
+ raise TypeError(self.daemon)
133
+
134
+ #
135
+
136
+ _lock: threading.RLock = dc.field(default_factory=lambda: threading.RLock(), init=False)
@@ -1,4 +1,5 @@
1
1
  import abc
2
+ import enum
2
3
  import functools
3
4
  import os
4
5
  import sys
@@ -9,6 +10,7 @@ from .. import check
9
10
  from .. import dataclasses as dc
10
11
  from .. import lang
11
12
  from ..diag import pydevd
13
+ from .targets import Target
12
14
 
13
15
 
14
16
  if ta.TYPE_CHECKING:
@@ -37,6 +39,8 @@ class Spawn(dc.Frozen, final=True):
37
39
 
38
40
  _: dc.KW_ONLY
39
41
 
42
+ target: Target | None = None
43
+
40
44
  inherit_fds: ta.Collection[int] | None = None
41
45
 
42
46
 
@@ -59,10 +63,29 @@ def spawner_for(spawning: Spawning) -> Spawner:
59
63
 
60
64
 
61
65
  class MultiprocessingSpawning(Spawning, kw_only=True):
66
+ class StartMethod(enum.Enum):
67
+ SPAWN = enum.auto()
68
+ FORK = enum.auto()
69
+ # TODO: FORK_SERVER
70
+
62
71
  # Defaults to 'fork' if under pydevd, else 'spawn'
63
- start_method: str | None = None
72
+ start_method: StartMethod | None = None
73
+
74
+ #
75
+
76
+ # Note: Per multiprocessing docs, `no_linger=True` processes (corresponding to `Process(daemon=True)`) cannot spawn
77
+ # subprocesses, and thus will fail if `Daemon.Config.reparent_process` is set.
78
+ no_linger: bool = False
64
79
 
65
- non_daemon: bool = False
80
+ #
81
+
82
+ @dc.dataclass(frozen=True, kw_only=True)
83
+ class EntrypointArgs:
84
+ spawning: 'MultiprocessingSpawning'
85
+ spawn: Spawn
86
+ start_method: 'MultiprocessingSpawning.StartMethod'
87
+
88
+ entrypoint: ta.Callable[[EntrypointArgs], None] | None = None
66
89
 
67
90
 
68
91
  class MultiprocessingSpawner(Spawner):
@@ -72,20 +95,26 @@ class MultiprocessingSpawner(Spawner):
72
95
  self._spawning = spawning
73
96
  self._process: ta.Optional['mp.process.BaseProcess'] = None # noqa
74
97
 
98
+ @lang.cached_function
99
+ def _determine_start_method(self) -> 'MultiprocessingSpawning.StartMethod':
100
+ if (start_method := self._spawning.start_method) is not None:
101
+ return start_method
102
+
103
+ # Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
104
+ # https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
105
+ if pydevd.is_running():
106
+ return MultiprocessingSpawning.StartMethod.FORK
107
+
108
+ return MultiprocessingSpawning.StartMethod.SPAWN
109
+
75
110
  def _process_cls(self, spawn: Spawn) -> type['mp.process.BaseProcess']:
76
- if (start_method := self._spawning.start_method) is None:
77
- # Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
78
- # https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
79
- if pydevd.is_running():
80
- start_method = 'fork'
81
- else:
82
- start_method = 'spawn'
111
+ start_method = self._determine_start_method()
83
112
 
84
113
  ctx: 'mp.context.BaseContext' # noqa
85
- if start_method == 'fork':
86
- ctx = mp.get_context(check.non_empty_str(start_method))
114
+ if start_method == MultiprocessingSpawning.StartMethod.FORK:
115
+ ctx = mp.get_context(check.non_empty_str('fork'))
87
116
 
88
- elif start_method == 'spawn':
117
+ elif start_method == MultiprocessingSpawning.StartMethod.SPAWN:
89
118
  ctx = omp_spawn.ExtrasSpawnContext(omp_spawn.SpawnExtras(
90
119
  pass_fds=frozenset(spawn.inherit_fds) if spawn.inherit_fds is not None else None,
91
120
  ))
@@ -97,9 +126,20 @@ class MultiprocessingSpawner(Spawner):
97
126
 
98
127
  def spawn(self, spawn: Spawn) -> None:
99
128
  check.none(self._process)
129
+
130
+ target: ta.Callable[[], None]
131
+ if (ep := self._spawning.entrypoint) is not None:
132
+ target = functools.partial(ep, MultiprocessingSpawning.EntrypointArgs(
133
+ spawning=self._spawning,
134
+ spawn=spawn,
135
+ start_method=self._determine_start_method(),
136
+ ))
137
+ else:
138
+ target = spawn.fn
139
+
100
140
  self._process = self._process_cls(spawn)(
101
- target=spawn.fn,
102
- daemon=not self._spawning.non_daemon,
141
+ target=target,
142
+ daemon=self._spawning.no_linger,
103
143
  )
104
144
  self._process.start()
105
145
 
@@ -142,7 +182,7 @@ def _(spawning: ForkSpawning) -> ForkSpawner:
142
182
 
143
183
 
144
184
  class ThreadSpawning(Spawning, kw_only=True):
145
- non_daemon: bool = False
185
+ linger: bool = False
146
186
 
147
187
 
148
188
  class ThreadSpawner(InProcessSpawner):
@@ -156,7 +196,7 @@ class ThreadSpawner(InProcessSpawner):
156
196
  check.none(self._thread)
157
197
  self._thread = threading.Thread(
158
198
  target=spawn.fn,
159
- daemon=not self._spawning.non_daemon,
199
+ daemon=not self._spawning.linger,
160
200
  )
161
201
  self._thread.start()
162
202
 
@@ -43,6 +43,7 @@ def make_simple_http_server(
43
43
  ignore_ssl_errors: bool = False,
44
44
  executor: ta.Optional[cf.Executor] = None,
45
45
  use_threads: bool = False,
46
+ **kwargs: ta.Any,
46
47
  ) -> ta.Iterator[SocketServer]:
47
48
  check.arg(not (executor is not None and use_threads))
48
49
 
@@ -107,6 +108,7 @@ def make_simple_http_server(
107
108
  server = es.enter_context(SocketServer(
108
109
  SocketBinder.of(bind),
109
110
  server_handler,
111
+ **kwargs,
110
112
  ))
111
113
 
112
114
  yield server
omlish/lang/__init__.py CHANGED
@@ -1,39 +1,63 @@
1
- from .cached import ( # noqa
1
+ from .attrs import ( # noqa
2
+ AttrOps,
3
+ STD_ATTR_OPS,
4
+ StdAttrOps,
5
+ TRANSIENT_ATTR_OPS,
6
+ TransientAttrOps,
7
+ TransientDict,
8
+ transient_delattr,
9
+ transient_getattr,
10
+ transient_setattr,
11
+ )
12
+
13
+ from .cached.function import ( # noqa
2
14
  cached_function,
3
- cached_property,
4
15
  static_init,
5
16
  )
6
17
 
7
- from .classes import ( # noqa
18
+ from .cached.property import ( # noqa
19
+ cached_property,
20
+ )
21
+
22
+ from .classes.abstract import ( # noqa
8
23
  Abstract,
9
24
  AbstractTypeError,
25
+ get_abstract_methods,
26
+ is_abstract,
27
+ is_abstract_class,
28
+ is_abstract_method,
29
+ make_abstract,
30
+ unabstract_class,
31
+ )
32
+
33
+ from .classes.restrict import ( # noqa
10
34
  AnySensitive,
11
- Callable,
12
- Descriptor,
13
35
  Final,
14
36
  FinalTypeError,
15
- LazySingleton,
16
- Marker,
17
- Namespace,
18
37
  NoBool,
19
38
  NotInstantiable,
20
39
  NotPicklable,
21
40
  PackageSealed,
22
- Picklable,
23
41
  SENSITIVE_ATTR,
24
42
  Sealed,
25
43
  SealedError,
26
44
  Sensitive,
45
+ no_bool,
46
+ )
47
+
48
+ from .classes.simple import ( # noqa
49
+ LazySingleton,
50
+ Marker,
51
+ Namespace,
27
52
  SimpleMetaDict,
28
53
  Singleton,
54
+ )
55
+
56
+ from .classes.virtual import ( # noqa
57
+ Callable,
58
+ Descriptor,
59
+ Picklable,
29
60
  Virtual,
30
- get_abstract_methods,
31
- is_abstract,
32
- is_abstract_class,
33
- is_abstract_method,
34
- make_abstract,
35
- no_bool,
36
- unabstract_class,
37
61
  virtual_check,
38
62
  )
39
63
 
omlish/lang/attrs.py ADDED
@@ -0,0 +1,132 @@
1
+ import abc
2
+ import collections.abc
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ class AttrOps(abc.ABC):
10
+ class NOT_SET: # noqa
11
+ def __new__(cls, *args, **kwargs): # noqa
12
+ raise TypeError
13
+
14
+ @abc.abstractmethod
15
+ def getattr(self, obj: ta.Any, name: str, default: ta.Any = NOT_SET) -> ta.Any:
16
+ raise NotImplementedError
17
+
18
+ @abc.abstractmethod
19
+ def setattr(self, obj: ta.Any, name: str, value: ta.Any) -> None:
20
+ raise NotImplementedError
21
+
22
+ @abc.abstractmethod
23
+ def delattr(self, obj: ta.Any, name: str) -> None:
24
+ raise NotImplementedError
25
+
26
+
27
+ ##
28
+
29
+
30
+ class StdAttrOps(AttrOps):
31
+ def getattr(self, obj: ta.Any, name: str, default: ta.Any = AttrOps.NOT_SET) -> ta.Any:
32
+ if default is AttrOps.NOT_SET:
33
+ return getattr(obj, name)
34
+ else:
35
+ return getattr(obj, name, default)
36
+
37
+ def setattr(self, obj: ta.Any, name: str, value: ta.Any) -> None:
38
+ setattr(obj, name, value)
39
+
40
+ def delattr(self, obj: ta.Any, name: str) -> None:
41
+ delattr(obj, name)
42
+
43
+
44
+ STD_ATTR_OPS = StdAttrOps()
45
+
46
+
47
+ ##
48
+
49
+
50
+ class TransientDict(collections.abc.MutableMapping):
51
+ def __init__(self) -> None:
52
+ super().__init__()
53
+
54
+ self._dct: dict = {}
55
+
56
+ def __reduce__(self):
57
+ return (TransientDict, ())
58
+
59
+ def __getitem__(self, item):
60
+ return self._dct[item]
61
+
62
+ def __setitem__(self, key, value):
63
+ self._dct[key] = value
64
+
65
+ def __delitem__(self, key):
66
+ del self._dct[key]
67
+
68
+ def __len__(self):
69
+ return len(self._dct)
70
+
71
+ def __iter__(self):
72
+ return iter(self._dct)
73
+
74
+ def clear(self):
75
+ self._dct.clear()
76
+
77
+ def items(self):
78
+ return self._dct.items()
79
+
80
+ def keys(self):
81
+ return self._dct.keys()
82
+
83
+ def values(self):
84
+ return self._dct.values()
85
+
86
+ def __contains__(self, key, /):
87
+ return super().__contains__(key)
88
+
89
+ def __eq__(self, other, /):
90
+ raise TypeError(self)
91
+
92
+
93
+ #
94
+
95
+
96
+ _TRANSIENT_DICT_ATTR = '__transient_dict__'
97
+
98
+
99
+ def _get_object_transient_dict(obj: ta.Any) -> TransientDict:
100
+ try:
101
+ return obj.__dict__[_TRANSIENT_DICT_ATTR]
102
+ except KeyError:
103
+ return obj.__dict__.setdefault(_TRANSIENT_DICT_ATTR, TransientDict())
104
+
105
+
106
+ class TransientAttrOps(AttrOps):
107
+ def getattr(self, obj: ta.Any, name: str, default: ta.Any = AttrOps.NOT_SET) -> ta.Any:
108
+ td = _get_object_transient_dict(obj)
109
+ try:
110
+ return td[name]
111
+ except KeyError:
112
+ if default is not AttrOps.NOT_SET:
113
+ return default
114
+ raise AttributeError(name) from None
115
+
116
+ def setattr(self, obj: ta.Any, name: str, value: ta.Any) -> None:
117
+ td = _get_object_transient_dict(obj)
118
+ td[name] = value
119
+
120
+ def delattr(self, obj: ta.Any, name: str) -> None:
121
+ td = _get_object_transient_dict(obj)
122
+ try:
123
+ del td[name]
124
+ except KeyError:
125
+ raise AttributeError(name) from None
126
+
127
+
128
+ TRANSIENT_ATTR_OPS = TransientAttrOps()
129
+
130
+ transient_getattr = TRANSIENT_ATTR_OPS.getattr
131
+ transient_setattr = TRANSIENT_ATTR_OPS.setattr
132
+ transient_delattr = TRANSIENT_ATTR_OPS.delattr
File without changes
@@ -2,37 +2,42 @@
2
2
  TODO:
3
3
  - integrate / expose with collections.cache
4
4
  - weakrefs (selectable by arg)
5
- - locks
5
+ - more rigorous descriptor pickling
6
+ - must support free functions (which have no instance nor owner)
7
+ - 'staticmethod' or effective equiv - which must resolve to the shared instance
8
+ - and must be transient?
6
9
  """
7
10
  import dataclasses as dc
8
11
  import functools
9
12
  import inspect
10
13
  import typing as ta
11
14
 
12
- from .contextmanagers import DefaultLockable
13
- from .contextmanagers import default_lock
14
- from .descriptors import unwrap_func
15
- from .descriptors import unwrap_func_with_partials
15
+ from ..classes.abstract import Abstract
16
+ from ..contextmanagers import DefaultLockable
17
+ from ..contextmanagers import default_lock
18
+ from ..descriptors import unwrap_func
19
+ from ..descriptors import unwrap_func_with_partials
16
20
 
17
21
 
18
22
  P = ta.ParamSpec('P')
19
23
  T = ta.TypeVar('T')
20
24
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
21
25
 
22
- _IGNORE = object()
26
+
27
+ ##
23
28
 
24
29
 
25
- def _nullary_cache_keyer():
30
+ def _nullary_cache_key_maker():
26
31
  return ()
27
32
 
28
33
 
29
- def _simple_cache_keyer(*args, **kwargs):
34
+ def _simple_cache_key_maker(*args, **kwargs):
30
35
  return (args, tuple(sorted(kwargs.items())))
31
36
 
32
37
 
33
- def _make_cache_keyer(fn, *, simple=False, bound=False):
38
+ def _make_cache_key_maker(fn, *, simple=False, bound=False):
34
39
  if simple:
35
- return _simple_cache_keyer
40
+ return _simple_cache_key_maker
36
41
 
37
42
  fn, partials = unwrap_func_with_partials(fn)
38
43
 
@@ -42,7 +47,7 @@ def _make_cache_keyer(fn, *, simple=False, bound=False):
42
47
  sig = inspect.signature(fn)
43
48
  sig_params = list(sig.parameters.values())[1 if bound else 0:]
44
49
  if not sig_params:
45
- return _nullary_cache_keyer
50
+ return _nullary_cache_key_maker
46
51
 
47
52
  ns = {}
48
53
  src_params = []
@@ -92,19 +97,23 @@ def _make_cache_keyer(fn, *, simple=False, bound=False):
92
97
  return kfn
93
98
 
94
99
 
95
- class _CachedFunction(ta.Generic[T]):
100
+ ##
101
+
102
+
103
+ class _CachedFunction(ta.Generic[T], Abstract):
96
104
  @dc.dataclass(frozen=True)
97
105
  class Opts:
98
106
  map_maker: ta.Callable[[], ta.MutableMapping] = dict
99
107
  simple_key: bool = False
100
108
  lock: DefaultLockable = None
109
+ transient: bool = False
101
110
 
102
111
  def __init__(
103
112
  self,
104
113
  fn: ta.Callable[P, T],
105
114
  *,
106
115
  opts: Opts = Opts(),
107
- keyer: ta.Callable[..., tuple] | None = None,
116
+ key_maker: ta.Callable[..., tuple] | None = None,
108
117
  values: ta.MutableMapping | None = None,
109
118
  value_fn: ta.Callable[P, T] | None = None,
110
119
  ) -> None:
@@ -112,7 +121,7 @@ class _CachedFunction(ta.Generic[T]):
112
121
 
113
122
  self._fn = (fn,)
114
123
  self._opts = opts
115
- self._keyer = keyer if keyer is not None else _make_cache_keyer(fn, simple=opts.simple_key)
124
+ self._key_maker = key_maker if key_maker is not None else _make_cache_key_maker(fn, simple=opts.simple_key)
116
125
 
117
126
  self._lock = default_lock(opts.lock, False)() if opts.lock is not None else None
118
127
  self._values = values if values is not None else opts.map_maker()
@@ -134,7 +143,7 @@ class _CachedFunction(ta.Generic[T]):
134
143
  raise TypeError
135
144
 
136
145
  def __call__(self, *args, **kwargs) -> T:
137
- k = self._keyer(*args, **kwargs)
146
+ k = self._key_maker(*args, **kwargs)
138
147
 
139
148
  try:
140
149
  return self._values[k]
@@ -157,11 +166,42 @@ class _CachedFunction(ta.Generic[T]):
157
166
  return value
158
167
 
159
168
 
160
- class _CachedFunctionDescriptor(_CachedFunction[T]):
169
+ #
170
+
171
+
172
+ class _FreeCachedFunction(_CachedFunction[T]):
173
+ @classmethod
174
+ def _unpickle(
175
+ cls,
176
+ fn,
177
+ opts,
178
+ values,
179
+ ):
180
+ return cls(
181
+ fn,
182
+ opts=opts,
183
+ values=values,
184
+ )
185
+
186
+ def __reduce__(self):
187
+ return (
188
+ _FreeCachedFunction._unpickle,
189
+ (
190
+ self._fn,
191
+ self._opts,
192
+ self._values if not self._opts.transient else None,
193
+ ),
194
+ )
195
+
196
+
197
+ #
198
+
199
+
200
+ class _DescriptorCachedFunction(_CachedFunction[T]):
161
201
  def __init__(
162
202
  self,
163
203
  fn: ta.Callable[P, T],
164
- scope: ta.Any,
204
+ scope: ta.Any, # classmethod | None
165
205
  *,
166
206
  instance: ta.Any = None,
167
207
  owner: ta.Any = None,
@@ -174,9 +214,61 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
174
214
  self._instance = instance
175
215
  self._owner = owner
176
216
  self._name = name if name is not None else unwrap_func(fn).__name__
177
- self._bound_keyer = None
217
+ self._bound_key_maker = None
178
218
 
179
- def __get__(self, instance, owner=None):
219
+ @classmethod
220
+ def _unpickle(
221
+ cls,
222
+ scope,
223
+ instance,
224
+ owner,
225
+ name,
226
+ values,
227
+ ):
228
+ if scope is not None:
229
+ raise NotImplementedError
230
+
231
+ if instance is None:
232
+ raise RuntimeError
233
+ obj = type(instance)
234
+
235
+ desc: _DescriptorCachedFunction = object.__getattribute__(obj, name)
236
+ if not isinstance(desc, cls):
237
+ raise TypeError(desc)
238
+ if (desc._instance is not None or desc._owner is not None):
239
+ raise RuntimeError
240
+
241
+ return desc._bind(
242
+ instance,
243
+ owner,
244
+ values=values,
245
+ )
246
+
247
+ def __reduce__(self):
248
+ if self._scope is not None:
249
+ raise NotImplementedError
250
+
251
+ if not (self._instance is not None or self._owner is not None):
252
+ raise RuntimeError
253
+
254
+ return (
255
+ _DescriptorCachedFunction._unpickle,
256
+ (
257
+ self._scope,
258
+ self._instance,
259
+ self._owner,
260
+ self._name,
261
+ self._values if not self._opts.transient else None,
262
+ ),
263
+ )
264
+
265
+ def _bind(
266
+ self,
267
+ instance,
268
+ owner=None,
269
+ *,
270
+ values: ta.MutableMapping | None = None,
271
+ ):
180
272
  scope = self._scope
181
273
  if owner is self._owner and (instance is self._instance or scope is classmethod):
182
274
  return self
@@ -184,8 +276,8 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
184
276
  fn, = self._fn
185
277
  name = self._name
186
278
  bound_fn = fn.__get__(instance, owner)
187
- if self._bound_keyer is None:
188
- self._bound_keyer = _make_cache_keyer(fn, simple=self._opts.simple_key, bound=True)
279
+ if self._bound_key_maker is None:
280
+ self._bound_key_maker = _make_cache_key_maker(fn, simple=self._opts.simple_key, bound=True)
189
281
 
190
282
  bound = self.__class__(
191
283
  fn,
@@ -194,8 +286,9 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
194
286
  instance=instance,
195
287
  owner=owner,
196
288
  name=name,
197
- keyer=self._bound_keyer,
198
- # values=None if scope is classmethod else self._values,
289
+ key_maker=self._bound_key_maker,
290
+ # values=None if scope is classmethod else self._values, # FIXME: ?
291
+ values=values,
199
292
  value_fn=bound_fn,
200
293
  )
201
294
 
@@ -206,76 +299,24 @@ class _CachedFunctionDescriptor(_CachedFunction[T]):
206
299
 
207
300
  return bound
208
301
 
302
+ def __get__(self, instance, owner=None):
303
+ return self._bind(instance, owner)
304
+
305
+
306
+ #
307
+
209
308
 
210
309
  def cached_function(fn=None, **kwargs): # noqa
211
310
  if fn is None:
212
311
  return functools.partial(cached_function, **kwargs)
213
312
  opts = _CachedFunction.Opts(**kwargs)
214
313
  if isinstance(fn, staticmethod):
215
- return _CachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
314
+ return _FreeCachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
216
315
  scope = classmethod if isinstance(fn, classmethod) else None
217
- return _CachedFunctionDescriptor(fn, scope, opts=opts)
316
+ return _DescriptorCachedFunction(fn, scope, opts=opts)
218
317
 
219
318
 
220
319
  def static_init(fn: CallableT) -> CallableT:
221
320
  fn = cached_function(fn)
222
321
  fn()
223
322
  return fn
224
-
225
-
226
- ##
227
-
228
-
229
- class _CachedProperty(property):
230
- def __init__(
231
- self,
232
- fn,
233
- *,
234
- name=None,
235
- ignore_if=lambda _: False,
236
- clear_on_init=False,
237
- ):
238
- if isinstance(fn, property):
239
- fn = fn.fget
240
- super().__init__(fn)
241
- self._fn = fn
242
- self._ignore_if = ignore_if
243
- self._name = name
244
- self._clear_on_init = clear_on_init
245
-
246
- def __set_name__(self, owner, name):
247
- if self._name is None:
248
- self._name = name
249
-
250
- def __get__(self, instance, owner=None):
251
- if instance is None:
252
- return self
253
- if self._name is None:
254
- raise TypeError(self)
255
-
256
- try:
257
- return instance.__dict__[self._name]
258
- except KeyError:
259
- pass
260
-
261
- value = self._fn.__get__(instance, owner)()
262
- if value is _IGNORE:
263
- return None
264
- instance.__dict__[self._name] = value
265
- return value
266
-
267
- def __set__(self, instance, value):
268
- if self._ignore_if(value):
269
- return
270
- if instance.__dict__[self._name] == value:
271
- return
272
- raise TypeError(self._name)
273
-
274
- def __delete__(self, instance):
275
- raise TypeError
276
-
277
-
278
- def cached_property(fn=None, **kwargs): # noqa
279
- if fn is None:
280
- return functools.partial(cached_property, **kwargs)
281
- return _CachedProperty(fn, **kwargs)
@@ -0,0 +1,118 @@
1
+ import abc
2
+ import functools
3
+ import typing as ta
4
+
5
+ from ..attrs import transient_getattr
6
+ from ..attrs import transient_setattr
7
+ from ..classes.abstract import Abstract
8
+
9
+
10
+ _IGNORE = object()
11
+
12
+
13
+ ##
14
+
15
+
16
+ class _CachedProperty(property, Abstract):
17
+ def __init__(
18
+ self,
19
+ fn,
20
+ *,
21
+ name=None,
22
+ ignore_if=lambda _: False,
23
+ ):
24
+ if isinstance(fn, property):
25
+ fn = fn.fget
26
+
27
+ super().__init__(fn)
28
+
29
+ self._fn = fn
30
+ self._ignore_if = ignore_if
31
+
32
+ self._name = name
33
+
34
+ def __set_name__(self, owner, name):
35
+ if self._name is None:
36
+ self._name = name
37
+
38
+ @abc.abstractmethod
39
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
40
+ raise NotImplementedError
41
+
42
+ @abc.abstractmethod
43
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
44
+ raise NotImplementedError
45
+
46
+ def __get__(self, instance, owner=None):
47
+ if instance is None:
48
+ return self
49
+ if self._name is None:
50
+ raise TypeError(self)
51
+
52
+ value, ok = self._instance_get(instance)
53
+ if ok:
54
+ return value
55
+
56
+ value = self._fn.__get__(instance, owner)()
57
+ if value is _IGNORE:
58
+ return None
59
+
60
+ self._instance_set(instance, value)
61
+ return value
62
+
63
+ def __set__(self, instance, value):
64
+ if self._ignore_if(value):
65
+ return
66
+
67
+ ev, ok = self._instance_get(instance)
68
+ if ok and ev == value:
69
+ return
70
+
71
+ raise TypeError(self._name)
72
+
73
+ def __delete__(self, instance):
74
+ raise TypeError
75
+
76
+
77
+ #
78
+
79
+
80
+ class _DictCachedProperty(_CachedProperty):
81
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
82
+ try:
83
+ value = instance.__dict__[self._name]
84
+ except KeyError:
85
+ return None, False
86
+ else:
87
+ return value, True
88
+
89
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
90
+ instance.__dict__[self._name] = value
91
+
92
+
93
+ #
94
+
95
+
96
+ class _TransientCachedProperty(_CachedProperty):
97
+ def _instance_get(self, instance: ta.Any) -> tuple[ta.Any, bool]:
98
+ try:
99
+ value = transient_getattr(instance, self._name)
100
+ except AttributeError:
101
+ return None, False
102
+ else:
103
+ return value, True
104
+
105
+ def _instance_set(self, instance: ta.Any, value: ta.Any) -> None:
106
+ transient_setattr(instance, self._name, value)
107
+
108
+
109
+ #
110
+
111
+
112
+ def cached_property(fn=None, *, transient=False, **kwargs): # noqa
113
+ if fn is None:
114
+ return functools.partial(cached_property, transient=transient, **kwargs)
115
+ if transient:
116
+ return _TransientCachedProperty(fn, **kwargs)
117
+ else:
118
+ return _DictCachedProperty(fn, **kwargs)
@@ -1,41 +0,0 @@
1
- from .abstract import ( # noqa
2
- Abstract,
3
- AbstractTypeError,
4
- get_abstract_methods,
5
- is_abstract,
6
- is_abstract_class,
7
- is_abstract_method,
8
- make_abstract,
9
- unabstract_class,
10
- )
11
-
12
- from .restrict import ( # noqa
13
- AnySensitive,
14
- Final,
15
- FinalTypeError,
16
- NoBool,
17
- NotInstantiable,
18
- NotPicklable,
19
- PackageSealed,
20
- SENSITIVE_ATTR,
21
- Sealed,
22
- SealedError,
23
- Sensitive,
24
- no_bool,
25
- )
26
-
27
- from .simple import ( # noqa
28
- LazySingleton,
29
- Marker,
30
- Namespace,
31
- SimpleMetaDict,
32
- Singleton,
33
- )
34
-
35
- from .virtual import ( # noqa
36
- Callable,
37
- Descriptor,
38
- Picklable,
39
- Virtual,
40
- virtual_check,
41
- )
omlish/sockets/wait.py CHANGED
@@ -8,13 +8,38 @@ from ..lite.timeouts import Timeout
8
8
  from ..lite.timeouts import TimeoutLike
9
9
 
10
10
 
11
+ ##
12
+
13
+
14
+ def socket_can_connect(
15
+ address: ta.Any,
16
+ *,
17
+ timeout: ta.Optional[TimeoutLike] = None,
18
+ on_fail: ta.Optional[ta.Callable[[BaseException], None]] = None,
19
+ exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (ConnectionRefusedError,),
20
+ ) -> bool:
21
+ timeout = Timeout.of(timeout)
22
+
23
+ try:
24
+ conn = socket.create_connection(address, timeout=timeout.or_(None))
25
+
26
+ except exception as e: # noqa
27
+ if on_fail is not None:
28
+ on_fail(e)
29
+ return False
30
+
31
+ else:
32
+ conn.close()
33
+ return True
34
+
35
+
11
36
  def socket_wait_until_can_connect(
12
37
  address: ta.Any,
13
38
  *,
14
39
  timeout: ta.Optional[TimeoutLike] = None,
15
40
  on_fail: ta.Optional[ta.Callable[[BaseException], None]] = None,
16
41
  sleep_s: float = .1,
17
- exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (Exception,),
42
+ exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (ConnectionRefusedError,),
18
43
  cancel_event: ta.Optional[threading.Event] = None,
19
44
  ) -> None:
20
45
  timeout = Timeout.of(timeout)
@@ -25,15 +50,12 @@ def socket_wait_until_can_connect(
25
50
  while not cancel_event.is_set():
26
51
  timeout()
27
52
 
28
- try:
29
- conn = socket.create_connection(address, timeout=timeout.or_(None))
30
-
31
- except exception as e: # noqa
32
- if on_fail is not None:
33
- on_fail(e)
34
-
35
- else:
36
- conn.close()
53
+ if socket_can_connect(
54
+ address,
55
+ timeout=timeout,
56
+ on_fail=on_fail,
57
+ exception=exception,
58
+ ):
37
59
  break
38
60
 
39
61
  cancel_event.wait(min(sleep_s, timeout.remaining()))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev243
3
+ Version: 0.0.0.dev245
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,8 +1,8 @@
1
1
  omlish/.manifests.json,sha256=vQTAIvR8OblSq-uP2GUfnbei0RnmAnM5j0T1-OToh9E,8253
2
- omlish/__about__.py,sha256=3c_F9MxYda9rfa68tINgteoJ57COTQpld-0xkq1JRwY,3380
2
+ omlish/__about__.py,sha256=igneh_Z18EcRdtPlDRmK5tHmRK3NpxQPFoUopKrrNtw,3380
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
5
- omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
5
+ omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
6
6
  omlish/check.py,sha256=THqm6jD1a0skAO5EC8SOVg58yq96Vk5wcuruBkCYxyU,2016
7
7
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
8
8
  omlish/defs.py,sha256=9uUjJuVIbCBL3g14fyzAp-9gH935MFofvlfOGwcBIaM,4913
@@ -180,11 +180,11 @@ omlish/configs/processing/names.py,sha256=weHmaTclzgM9lUn3aBtw-kwZ3mc2N-CZlFg3Kd
180
180
  omlish/configs/processing/rewriting.py,sha256=v7PfHtuTn5v_5Y6Au7oMN2Z0nxAMy1iYyO5CXnTvZhs,4226
181
181
  omlish/configs/processing/strings.py,sha256=qFS2oh6z02IaM_q4lTKLdufzkJqAJ6J-Qjrz5S-QJoM,826
182
182
  omlish/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
183
- omlish/daemons/daemon.py,sha256=ykdbCPbpxKrdZVZc892SnedTdftTAYt_YdDpYchKcUE,3410
184
- omlish/daemons/launching.py,sha256=mhtkuAO16STcznUl3rrX9pacfrKbPQRCP2AllKL4B70,3664
185
- omlish/daemons/reparent.py,sha256=UaG2X6VJHJPOlUwHPNRH3aWGgF0Fg771jjO9IRPLlyY,280
186
- omlish/daemons/services.py,sha256=UAzzdP4jG0-piVzz6CsSTPIjTGt4VFXtbzP7KczMCho,2354
187
- omlish/daemons/spawning.py,sha256=cx00xeqSrfhlFbjCtKqaBHvMuHwB9hdjuKNHzAAo_dw,4030
183
+ omlish/daemons/daemon.py,sha256=3Wkvu8M_EaCKSpKI5UN5OayRXV0oVdF62tBss9_hlr0,3479
184
+ omlish/daemons/launching.py,sha256=sNOYW939IGI4ZlLQ0bKxzXj6EyeOiwV7Upqhd5XfoHc,3747
185
+ omlish/daemons/reparent.py,sha256=7uJ9oPGt9Ud7uA8bDl_SHcuqjcsmXa3kkjp9jf29wOw,585
186
+ omlish/daemons/services.py,sha256=jIlGWhiWoqQlm_OFeffkSs9jjr-icDF1-I-SNMEgg9Y,3406
187
+ omlish/daemons/spawning.py,sha256=psR73zOYjMKTqNpx1bMib8uU9wAZz62tw5TaWHrTdyY,5337
188
188
  omlish/daemons/targets.py,sha256=00KmtlknMhQ5PyyVAhWl3rpeTMPym0GxvHHq6mYPZ7c,3051
189
189
  omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
190
190
  omlish/dataclasses/__init__.py,sha256=b7EZCIfHnEHCHWwgD3YXxkdsU-uYd9iD4hM36RgpI1g,1598
@@ -333,7 +333,7 @@ omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
333
333
  omlish/http/coro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
334
334
  omlish/http/coro/fdio.py,sha256=bd9K4EYVWbXV3e3npDPXI9DuDAruJiyDmrgFpgNcjzY,4035
335
335
  omlish/http/coro/server.py,sha256=30FTcJG8kuFeThf0HJYpTzMZN-giLTBP7wr5Wl3b9X0,18285
336
- omlish/http/coro/simple.py,sha256=_ZKFlfLda9Gatd3bNBNGJpITNQl4tuTAbL3P-Mr5j5w,3152
336
+ omlish/http/coro/simple.py,sha256=inWA_ss6Nz5Rqmy4dL9_SGah4anYoDecDTRQqVIGYeY,3200
337
337
  omlish/http/coro/sockets.py,sha256=rtpZZ-XCOfC5tXr4Fmo1HSn-8f5nxfIOlJaPUkQeDyU,1654
338
338
  omlish/inject/__init__.py,sha256=n0RC9UDGsBQQ39cST39-XJqJPq2M0tnnh9yJubW9azo,1891
339
339
  omlish/inject/binder.py,sha256=DAbc8TZi5w8Mna0TUtq0mT4jeDVA7i7SlBtOFrh2swc,4185
@@ -399,8 +399,8 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
399
399
  omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
400
400
  omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
401
401
  omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
402
- omlish/lang/__init__.py,sha256=T_hx_ygi-IqmeDPQ-uto2U4ZuAUDs-agOSAsF6WWLVI,4193
403
- omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
402
+ omlish/lang/__init__.py,sha256=0LNdzzVTKiDEmjCzT9wUPbimcAu21KYe-EwD4xDgtRM,4592
403
+ omlish/lang/attrs.py,sha256=vGYdGnS7ZAbSz0ZSZkkZKHCK1tKoNjZY0JdzFraHiUw,3105
404
404
  omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
405
405
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
406
406
  omlish/lang/contextmanagers.py,sha256=UPH6daYwSP9cH5AfSVsJyEHk1UURMGhVPM5ZRhp_Hvw,7576
@@ -418,7 +418,10 @@ omlish/lang/resources.py,sha256=N64KeVE-rYMxqBBRp91qzgVqpOVR2uX7k1WlS_bo5hM,2681
418
418
  omlish/lang/strings.py,sha256=egdv8PxLNG40-5V93agP5j2rBUDIsahCx048zV7uEbU,4690
419
419
  omlish/lang/sys.py,sha256=UoZz_PJYVKLQAKqYxxn-LHz1okK_38I__maZgnXMcxU,406
420
420
  omlish/lang/typing.py,sha256=Zdad9Zv0sa-hIaUXPrzPidT7sDVpRcussAI7D-j-I1c,3296
421
- omlish/lang/classes/__init__.py,sha256=HY9UD3Tg8_sH59emZUKgP1Kb8-8r0GCw5hUOumxwKlM,658
421
+ omlish/lang/cached/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
+ omlish/lang/cached/function.py,sha256=k3jRNuDtFh9Mk97oonC7nq5XRros2qmRJVWviMFrx74,8728
423
+ omlish/lang/cached/property.py,sha256=kzbao_35PlszdK_9oJBWrMmFFlVK_Xhx7YczHhTJ6cc,2764
424
+ omlish/lang/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
425
  omlish/lang/classes/abstract.py,sha256=bIcuAetV_aChhpSURypmjjcqP07xi20uVYPKh1kvQNU,3710
423
426
  omlish/lang/classes/restrict.py,sha256=QvM-GqXvO8vpCONkcr0QpgVPfzZmgDo7o8d9kUwOzlo,3941
424
427
  omlish/lang/classes/simple.py,sha256=XQ8b86WvQA0qtSYqlbMOJS7tHgE8sv9onda33uQmbkM,3294
@@ -566,7 +569,7 @@ omlish/sockets/bind.py,sha256=J1SfFFFnVf3H5nqESDX2NGEY8DmjyIMUXZciZM33zQY,8003
566
569
  omlish/sockets/handlers.py,sha256=Gj6xZoo4vommge8XvkehYw3B7O4aql2P4qzZIIa0p24,462
567
570
  omlish/sockets/io.py,sha256=lfhTkB7NnAIx9kuQhAkwgsEUXY78Mp1_WtYrIQNS_k8,1408
568
571
  omlish/sockets/ports.py,sha256=Wm4mRFFz5MdD8KbdaEfT1c4PbJnsuK_iyJlZJE_-8jo,1402
569
- omlish/sockets/wait.py,sha256=FSHzLR66RESMwHCV-Bu_mZWNNskRGfEwvM9q1arapV0,1049
572
+ omlish/sockets/wait.py,sha256=aznyOzGa9oNBc31xnyk1S7TylO8hGx7vFyOVsdY3zFE,1585
570
573
  omlish/sockets/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
571
574
  omlish/sockets/server/handlers.py,sha256=PPsb1X5oU9dN8jfztaMGsRiqWTyEANT-1aSLbS6bUVg,3867
572
575
  omlish/sockets/server/server.py,sha256=FkaishIxJuU4it9tTI7wzlGqJYzFGXzDrd_HgV0jAmU,6253
@@ -729,9 +732,9 @@ omlish/text/mangle.py,sha256=kfzFLfvepH-chl1P89_mdc5vC4FSqyPA2aVtgzuB8IY,1133
729
732
  omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
730
733
  omlish/text/parts.py,sha256=JkNZpyR2tv2CNcTaWJJhpQ9E4F0yPR8P_YfDbZfMtwQ,6182
731
734
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
732
- omlish-0.0.0.dev243.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
733
- omlish-0.0.0.dev243.dist-info/METADATA,sha256=dvum371m78Sb6B5v3K-C8hyOFzA8rEjIQUyy8GZH2G0,4176
734
- omlish-0.0.0.dev243.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
735
- omlish-0.0.0.dev243.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
736
- omlish-0.0.0.dev243.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
737
- omlish-0.0.0.dev243.dist-info/RECORD,,
735
+ omlish-0.0.0.dev245.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
736
+ omlish-0.0.0.dev245.dist-info/METADATA,sha256=JjIb3VY1vdwMkndkHrbTmD72X00OMiCJVwBpn07rc88,4176
737
+ omlish-0.0.0.dev245.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
738
+ omlish-0.0.0.dev245.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
739
+ omlish-0.0.0.dev245.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
740
+ omlish-0.0.0.dev245.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5