omlish 0.0.0.dev21__py3-none-any.whl → 0.0.0.dev23__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.
@@ -0,0 +1,11 @@
1
+ from .executors import ( # noqa
2
+ ImmediateExecutor,
3
+ new_executor,
4
+ )
5
+
6
+ from .futures import ( # noqa
7
+ FutureError,
8
+ FutureTimeoutError,
9
+ wait_futures,
10
+ wait_dependent_futures,
11
+ )
@@ -0,0 +1,52 @@
1
+ import concurrent.futures as cf
2
+ import contextlib
3
+ import typing as ta
4
+
5
+
6
+ T = ta.TypeVar('T')
7
+ P = ta.ParamSpec('P')
8
+
9
+
10
+ class ImmediateExecutor(cf.Executor):
11
+
12
+ def __init__(self, *, immediate_exceptions: bool = False) -> None:
13
+ super().__init__()
14
+ self._immediate_exceptions = immediate_exceptions
15
+
16
+ def submit(
17
+ self,
18
+ fn: ta.Callable[P, T],
19
+ /,
20
+ *args: P.args,
21
+ **kwargs: P.kwargs,
22
+ ) -> cf.Future[T]:
23
+ future: ta.Any = cf.Future()
24
+ try:
25
+ result = fn(*args, **kwargs)
26
+ future.set_result(result)
27
+ except Exception as e:
28
+ if self._immediate_exceptions:
29
+ raise
30
+ future.set_exception(e)
31
+ return future
32
+
33
+
34
+ @contextlib.contextmanager
35
+ def new_executor(
36
+ max_workers: int | None = None,
37
+ cls: type[cf.Executor] = cf.ThreadPoolExecutor,
38
+ *,
39
+ immediate_exceptions: bool = False,
40
+ **kwargs: ta.Any,
41
+ ) -> ta.Generator[cf.Executor, None, None]:
42
+ if max_workers == 0:
43
+ yield ImmediateExecutor(
44
+ immediate_exceptions=immediate_exceptions,
45
+ )
46
+
47
+ else:
48
+ with cls( # type: ignore
49
+ max_workers,
50
+ **kwargs,
51
+ ) as exe:
52
+ yield exe
@@ -1,11 +1,9 @@
1
1
  import concurrent.futures as cf
2
- import contextlib
3
2
  import time
4
3
  import typing as ta
5
4
 
6
5
 
7
6
  T = ta.TypeVar('T')
8
- P = ta.ParamSpec('P')
9
7
 
10
8
 
11
9
  ##
@@ -137,45 +135,3 @@ def wait_dependent_futures(
137
135
 
138
136
  futs_by_fn = {fn: fut for fut, fn in fns_by_fut.items()}
139
137
  return futs_by_fn
140
-
141
-
142
- ##
143
-
144
-
145
- class ImmediateExecutor(cf.Executor):
146
-
147
- def __init__(self, *, immediate_exceptions: bool = False) -> None:
148
- super().__init__()
149
- self._immediate_exceptions = immediate_exceptions
150
-
151
- def submit(self, fn: ta.Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> cf.Future[T]:
152
- future: ta.Any = cf.Future()
153
- try:
154
- result = fn(*args, **kwargs)
155
- future.set_result(result)
156
- except Exception as e:
157
- if self._immediate_exceptions:
158
- raise
159
- future.set_exception(e)
160
- return future
161
-
162
-
163
- @contextlib.contextmanager
164
- def new_executor(
165
- max_workers: int | None = None,
166
- cls: type[cf.Executor] = cf.ThreadPoolExecutor,
167
- *,
168
- immediate_exceptions: bool = False,
169
- **kwargs: ta.Any,
170
- ) -> ta.Generator[cf.Executor, None, None]:
171
- if max_workers == 0:
172
- yield ImmediateExecutor(
173
- immediate_exceptions=immediate_exceptions,
174
- )
175
-
176
- else:
177
- with cls( # type: ignore
178
- max_workers,
179
- **kwargs,
180
- ) as exe:
181
- yield exe
@@ -0,0 +1,91 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from omlish import lang
6
+
7
+
8
+ if ta.TYPE_CHECKING:
9
+ import greenlet
10
+ else:
11
+ greenlet = lang.proxy_import('greenlet')
12
+
13
+
14
+ ##
15
+
16
+
17
+ class Threadlet(abc.ABC):
18
+ """Not safe to identity-key - use `underlying`."""
19
+
20
+ def __hash__(self):
21
+ raise TypeError('use `underlying`')
22
+
23
+ def __eq__(self, other):
24
+ raise TypeError('use `underlying`')
25
+
26
+ @property
27
+ @abc.abstractmethod
28
+ def underlying(self) -> ta.Any:
29
+ raise NotImplementedError
30
+
31
+ @property
32
+ @abc.abstractmethod
33
+ def parent(self) -> ta.Optional['Threadlet']:
34
+ raise NotImplementedError
35
+
36
+ @property
37
+ @abc.abstractmethod
38
+ def dead(self) -> bool:
39
+ raise NotImplementedError
40
+
41
+ @abc.abstractmethod
42
+ def switch(self, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
43
+ raise NotImplementedError
44
+
45
+ @abc.abstractmethod
46
+ def throw(self, ex: Exception) -> ta.Any:
47
+ raise NotImplementedError
48
+
49
+
50
+ class Threadlets(abc.ABC):
51
+ @abc.abstractmethod
52
+ def spawn(self, fn: ta.Callable[[], None]) -> Threadlet:
53
+ raise NotImplementedError
54
+
55
+ @abc.abstractmethod
56
+ def get_current(self) -> Threadlet:
57
+ raise NotImplementedError
58
+
59
+
60
+ ##
61
+
62
+
63
+ @dc.dataclass(frozen=True)
64
+ class GreenletThreadlet(Threadlet):
65
+ g: 'greenlet.greenlet'
66
+
67
+ @property
68
+ def underlying(self) -> 'greenlet.greenlet':
69
+ return self.g
70
+
71
+ @property
72
+ def parent(self) -> ta.Optional['GreenletThreadlet']:
73
+ return GreenletThreadlet(self.g.parent)
74
+
75
+ @property
76
+ def dead(self) -> bool:
77
+ return self.g.dead
78
+
79
+ def switch(self, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
80
+ return self.g.switch(*args, **kwargs)
81
+
82
+ def throw(self, ex: Exception) -> ta.Any:
83
+ return self.g.throw(ex)
84
+
85
+
86
+ class GreenletThreadlets(Threadlets):
87
+ def spawn(self, fn: ta.Callable[[], None]) -> Threadlet:
88
+ return GreenletThreadlet(greenlet.greenlet(fn))
89
+
90
+ def get_current(self) -> Threadlet:
91
+ return GreenletThreadlet(greenlet.getcurrent())
omlish/diag/asts.py ADDED
@@ -0,0 +1,132 @@
1
+ import ast
2
+ import dataclasses as dc
3
+ import inspect
4
+ import pprint
5
+ import textwrap
6
+ import types
7
+ import typing as ta
8
+
9
+ from .. import lang
10
+
11
+
12
+ if ta.TYPE_CHECKING:
13
+ import executing
14
+ else:
15
+ executing = lang.proxy_import('executing')
16
+
17
+
18
+ class ArgsRenderer:
19
+ """
20
+ TODO:
21
+ - kwargs
22
+ - recursion
23
+ - whatever pytest looks like
24
+ - make sure not leaking sensitive data
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ origin: types.TracebackType | types.FrameType | None = None,
30
+ back: int = 1,
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self._origin = origin
35
+
36
+ self._frame: types.FrameType | None
37
+ if isinstance(origin, types.TracebackType):
38
+ self._frame = origin.tb_frame
39
+ elif isinstance(origin, types.FrameType):
40
+ self._frame = origin
41
+ elif origin is None:
42
+ frame = inspect.currentframe()
43
+ for _ in range(back + 1):
44
+ if frame is None:
45
+ break
46
+ frame = frame.f_back
47
+ self._frame = frame
48
+ else:
49
+ raise TypeError(origin)
50
+
51
+ def _get_indented_text(
52
+ self,
53
+ src: executing.Source,
54
+ node: ast.AST,
55
+ ) -> str:
56
+ result = src.asttokens().get_text(node)
57
+ if '\n' in result:
58
+ result = ' ' * node.first_token.start[1] + result # type: ignore
59
+ result = textwrap.dedent(result)
60
+ result = result.strip()
61
+ return result
62
+
63
+ def _val_to_string(self, obj: ta.Any) -> str:
64
+ s = pprint.pformat(obj)
65
+ s = s.replace('\\n', '\n')
66
+ return s
67
+
68
+ def _is_literal_expr(self, s: str) -> bool:
69
+ try:
70
+ ast.literal_eval(s)
71
+ except Exception: # noqa
72
+ return False
73
+ return True
74
+
75
+ @dc.dataclass(frozen=True)
76
+ class RenderedArg:
77
+ val: str
78
+ expr: str
79
+ is_literal_expr: bool
80
+
81
+ def __str__(self) -> str:
82
+ if self.is_literal_expr:
83
+ return self.val
84
+ else:
85
+ return f'({self.expr} = {self.val})'
86
+
87
+ def render_args(
88
+ self,
89
+ *vals: ta.Any,
90
+ ) -> ta.Sequence[RenderedArg] | None:
91
+ if self._frame is None:
92
+ return None
93
+
94
+ call_node = executing.Source.executing(self._frame).node
95
+ if not isinstance(call_node, ast.Call):
96
+ return None
97
+
98
+ source = executing.Source.for_frame(self._frame)
99
+
100
+ exprs = [
101
+ self._get_indented_text(source, arg) # noqa
102
+ for arg in call_node.args
103
+ ]
104
+ if len(exprs) != len(vals):
105
+ return None
106
+
107
+ return [
108
+ self.RenderedArg(
109
+ val=self._val_to_string(val),
110
+ expr=expr,
111
+ is_literal_expr=self._is_literal_expr(expr),
112
+ )
113
+ for val, expr in zip(vals, exprs)
114
+ ]
115
+
116
+ @classmethod
117
+ def smoketest(cls) -> bool:
118
+ def bar(z):
119
+ return z + 1
120
+
121
+ def foo(x, y):
122
+ return cls().render_args(x, y)
123
+
124
+ r = foo(1, bar(2))
125
+ if r is None:
126
+ return False
127
+
128
+ x, y = r
129
+ return (
130
+ x == cls.RenderedArg(val='1', expr='1', is_literal_expr=True) and
131
+ y == cls.RenderedArg(val='3', expr='bar(2)', is_literal_expr=False)
132
+ )
omlish/diag/pycharm.py ADDED
@@ -0,0 +1,80 @@
1
+ import os.path
2
+ import plistlib
3
+ import subprocess
4
+ import sys
5
+ import typing as ta
6
+
7
+ from .. import check
8
+ from .. import lang
9
+
10
+
11
+ if ta.TYPE_CHECKING:
12
+ docker = lang.proxy_import('omlish.docker')
13
+ else:
14
+ from omlish import docker
15
+
16
+
17
+ ##
18
+
19
+
20
+ PYCHARM_HOME = '/Applications/PyCharm.app'
21
+
22
+
23
+ def read_pycharm_info_plist() -> ta.Mapping[str, ta.Any] | None:
24
+ plist_file = os.path.join(PYCHARM_HOME, 'Contents', 'Info.plist')
25
+ if not os.path.isfile(plist_file):
26
+ return None
27
+
28
+ with open(plist_file, 'rb') as f:
29
+ root = plistlib.load(f)
30
+
31
+ return root
32
+
33
+
34
+ @lang.cached_function
35
+ def get_pycharm_version() -> str | None:
36
+ plist = read_pycharm_info_plist()
37
+ if plist is None:
38
+ return None
39
+
40
+ ver = check.non_empty_str(plist['CFBundleVersion'])
41
+ check.state(ver.startswith('PY-'))
42
+ return ver[3:]
43
+
44
+
45
+ ##
46
+
47
+
48
+ def pycharm_remote_debugger_attach(
49
+ host: str | None,
50
+ port: int,
51
+ *,
52
+ version: str | None = None,
53
+ ) -> None:
54
+ # if version is None:
55
+ # version = get_pycharm_version()
56
+ # check.non_empty_str(version)
57
+
58
+ if host is None:
59
+ if sys.platform == 'linux' and docker.is_likely_in_docker():
60
+ host = docker.DOCKER_FOR_MAC_HOSTNAME
61
+ else:
62
+ host = 'localhost'
63
+
64
+ try:
65
+ import pydevd_pycharm # noqa
66
+ except ImportError:
67
+ subprocess.check_call([
68
+ sys.executable,
69
+ '-mpip',
70
+ 'install',
71
+ 'pydevd-pycharm' + (f'~={version}' if version is not None else ''),
72
+ ])
73
+
74
+ import pydevd_pycharm # noqa
75
+ pydevd_pycharm.settrace(
76
+ host,
77
+ port=port,
78
+ stdoutToServer=True,
79
+ stderrToServer=True,
80
+ )
omlish/docker.py CHANGED
@@ -174,6 +174,9 @@ def timebomb_payload(delay_s: float, name: str = 'omlish-docker-timebomb') -> st
174
174
  ##
175
175
 
176
176
 
177
+ DOCKER_FOR_MAC_HOSTNAME = 'docker.for.mac.localhost'
178
+
179
+
177
180
  _LIKELY_IN_DOCKER_PATTERN = re.compile(r'^overlay / .*/docker/')
178
181
 
179
182
 
omlish/genmachine.py ADDED
@@ -0,0 +1,58 @@
1
+ """
2
+ https://github.com/pytransitions/transitions
3
+ """
4
+ import typing as ta
5
+
6
+
7
+ I = ta.TypeVar('I')
8
+ O = ta.TypeVar('O')
9
+
10
+ # MachineGen: ta.TypeAlias = ta.Generator[ta.Iterable[O] | None, I, ta.Optional[MachineGen[I, O]]]
11
+ MachineGen: ta.TypeAlias = ta.Generator[ta.Any, ta.Any, ta.Any]
12
+
13
+
14
+ ##
15
+
16
+
17
+ class IllegalStateError(Exception):
18
+ pass
19
+
20
+
21
+ class GenMachine(ta.Generic[I, O]):
22
+ """
23
+ Generator-powered state machine. Generators are sent an `I` object and yield any number of `O` objects in response,
24
+ until they yield a `None` by accepting new input. Generators may return a new generator to switch states, or return
25
+ `None` to terminate.
26
+ """
27
+
28
+ def __init__(self, initial: MachineGen) -> None:
29
+ super().__init__()
30
+ self._advance(initial)
31
+
32
+ @property
33
+ def state(self) -> str | None:
34
+ if self._gen is not None:
35
+ return self._gen.gi_code.co_qualname
36
+ return None
37
+
38
+ def __repr__(self) -> str:
39
+ return f'{self.__class__.__name__}@{hex(id(self))[2:]}<{self.state}>'
40
+
41
+ _gen: MachineGen | None
42
+
43
+ def _advance(self, gen: MachineGen) -> None:
44
+ self._gen = gen
45
+ if (n := next(self._gen)) is not None: # noqa
46
+ raise IllegalStateError
47
+
48
+ def __call__(self, i: I) -> ta.Iterable[O]:
49
+ if self._gen is None:
50
+ raise IllegalStateError
51
+ try:
52
+ while (o := self._gen.send(i)) is not None:
53
+ yield from o
54
+ except StopIteration as s:
55
+ if s.value is None:
56
+ self._gen = None
57
+ return None
58
+ self._advance(s.value)
omlish/lang/functions.py CHANGED
@@ -108,6 +108,9 @@ def issubclass_of(class_or_tuple: ta.Any) -> ta.Callable[[ta.Any], bool]:
108
108
  return lambda o: issubclass(o, class_or_tuple)
109
109
 
110
110
 
111
+ ##
112
+
113
+
111
114
  class VoidError(Exception):
112
115
  pass
113
116
 
omlish/logs/__init__.py CHANGED
@@ -8,6 +8,10 @@ from .formatters import ( # noqa
8
8
  StandardLogFormatter,
9
9
  )
10
10
 
11
+ from .handlers import ( # noqa
12
+ ListHandler,
13
+ )
14
+
11
15
  from .utils import ( # noqa
12
16
  error_logging,
13
17
  )
@@ -0,0 +1,10 @@
1
+ import logging
2
+
3
+
4
+ class ListHandler(logging.Handler):
5
+ def __init__(self) -> None:
6
+ super().__init__()
7
+ self.records: list[logging.LogRecord] = []
8
+
9
+ def emit(self, record):
10
+ self.records.append(record)
@@ -65,6 +65,10 @@ from .polymorphism import ( # noqa
65
65
  polymorphism_from_subclasses,
66
66
  )
67
67
 
68
+ from .primitives import ( # noqa
69
+ PRIMITIVE_TYPES,
70
+ )
71
+
68
72
  from .registries import ( # noqa
69
73
  Registry,
70
74
  )
omlish/marshal/base64.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ FIXME:
3
+ - don't base64 by default, only for json-esque targets
4
+ """
1
5
  import base64
2
6
  import typing as ta
3
7
 
@@ -9,6 +9,9 @@ from .base import Unmarshaler
9
9
  from .values import Value
10
10
 
11
11
 
12
+ ##
13
+
14
+
12
15
  PRIMITIVE_TYPES: tuple[type, ...] = (
13
16
  bool,
14
17
  int,
@@ -19,6 +22,9 @@ PRIMITIVE_TYPES: tuple[type, ...] = (
19
22
  )
20
23
 
21
24
 
25
+ ##
26
+
27
+
22
28
  class PrimitiveMarshalerUnmarshaler(Marshaler, Unmarshaler):
23
29
  def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
24
30
  if isinstance(o, PRIMITIVE_TYPES):
@@ -27,6 +27,8 @@ from .optionals import OptionalMarshalerFactory
27
27
  from .optionals import OptionalUnmarshalerFactory
28
28
  from .primitives import PRIMITIVE_MARSHALER_FACTORY
29
29
  from .primitives import PRIMITIVE_UNMARSHALER_FACTORY
30
+ from .unions import PrimitiveUnionMarshalerFactory
31
+ from .unions import PrimitiveUnionUnmarshalerFactory
30
32
  from .uuids import UUID_MARSHALER_FACTORY
31
33
  from .uuids import UUID_UNMARSHALER_FACTORY
32
34
 
@@ -37,6 +39,7 @@ from .uuids import UUID_UNMARSHALER_FACTORY
37
39
  STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
38
40
  PRIMITIVE_MARSHALER_FACTORY,
39
41
  OptionalMarshalerFactory(),
42
+ PrimitiveUnionMarshalerFactory(),
40
43
  DataclassMarshalerFactory(),
41
44
  EnumMarshalerFactory(),
42
45
  NUMBERS_MARSHALER_FACTORY,
@@ -66,6 +69,7 @@ def new_standard_marshaler_factory() -> MarshalerFactory:
66
69
  STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
67
70
  PRIMITIVE_UNMARSHALER_FACTORY,
68
71
  OptionalUnmarshalerFactory(),
72
+ PrimitiveUnionUnmarshalerFactory(),
69
73
  DataclassUnmarshalerFactory(),
70
74
  EnumUnmarshalerFactory(),
71
75
  NUMBERS_UNMARSHALER_FACTORY,
@@ -0,0 +1,101 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .. import check
5
+ from .. import matchfns as mfs
6
+ from .. import reflect as rfl
7
+ from .base import MarshalContext
8
+ from .base import Marshaler
9
+ from .base import MarshalerFactory
10
+ from .base import UnmarshalContext
11
+ from .base import Unmarshaler
12
+ from .base import UnmarshalerFactory
13
+ from .values import Value
14
+
15
+
16
+ ##
17
+
18
+
19
+ class MatchUnionMarshaler(Marshaler):
20
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
21
+
22
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
23
+ try:
24
+ m = self.mmf.match(ctx, o)
25
+ except mfs.AmbiguousMatchesError:
26
+ raise ValueError(o) # noqa
27
+ return m.fn(ctx, o)
28
+
29
+
30
+ class MatchUnionUnmarshaler(Unmarshaler):
31
+ mmf: mfs.MultiMatchFn[[UnmarshalContext, Value], ta.Any]
32
+
33
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
34
+ try:
35
+ m = self.mmf.match(ctx, v)
36
+ except mfs.AmbiguousMatchesError:
37
+ raise ValueError(v) # noqa
38
+ return m.fn(ctx, v)
39
+
40
+
41
+ ##
42
+
43
+
44
+ PRIMITIVE_UNION_TYPES: tuple[type, ...] = (
45
+ float,
46
+ int,
47
+ str,
48
+ )
49
+
50
+
51
+ #
52
+
53
+
54
+ @dc.dataclass(frozen=True)
55
+ class PrimitiveUnionMarshaler(Marshaler):
56
+ tys: ta.Sequence[type]
57
+
58
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
59
+ raise NotImplementedError
60
+
61
+
62
+ @dc.dataclass(frozen=True)
63
+ class PrimitiveUnionMarshalerFactory(MarshalerFactory):
64
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
65
+
66
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
67
+ return isinstance(rty, rfl.Union) and all(a in self.tys for a in rty.args)
68
+
69
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
70
+ args = check.isinstance(rty, rfl.Union).args
71
+ return PrimitiveUnionMarshaler([t for t in self.tys if t in args])
72
+
73
+
74
+ #
75
+
76
+
77
+ @dc.dataclass(frozen=True)
78
+ class PrimitiveUnionUnmarshaler(Unmarshaler):
79
+ tys: ta.Sequence[type]
80
+
81
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
82
+ raise NotImplementedError
83
+
84
+
85
+ @dc.dataclass(frozen=True)
86
+ class PrimitiveUnionUnmarshalerFactory(UnmarshalerFactory):
87
+ tys: ta.Sequence[type] = PRIMITIVE_UNION_TYPES
88
+
89
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
90
+ return isinstance(rty, rfl.Union) and all(a in self.tys for a in rty.args)
91
+
92
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
93
+ args = check.isinstance(rty, rfl.Union).args
94
+ return PrimitiveUnionUnmarshaler([t for t in self.tys if t in args])
95
+
96
+
97
+ #
98
+
99
+
100
+ PRIMITIVE_UNION_MARSHALER_FACTORY = PrimitiveUnionMarshalerFactory()
101
+ PRIMITIVE_UNION_UNMARSHALER_FACTORY = PrimitiveUnionUnmarshalerFactory()