omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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.

Potentially problematic release.


This version of omlish might be problematic. Click here for more details.

Files changed (143) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +1 -1
  3. omlish/asyncs/__init__.py +10 -4
  4. omlish/asyncs/anyio.py +142 -12
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/flavors.py +27 -1
  9. omlish/asyncs/trio_asyncio.py +28 -18
  10. omlish/c3.py +1 -1
  11. omlish/cached.py +1 -2
  12. omlish/collections/__init__.py +5 -1
  13. omlish/collections/cache/impl.py +1 -1
  14. omlish/collections/identity.py +7 -0
  15. omlish/collections/indexed.py +1 -1
  16. omlish/collections/utils.py +38 -6
  17. omlish/configs/__init__.py +5 -0
  18. omlish/configs/classes.py +53 -0
  19. omlish/configs/strings.py +94 -0
  20. omlish/dataclasses/__init__.py +9 -0
  21. omlish/dataclasses/impl/api.py +1 -1
  22. omlish/dataclasses/impl/as_.py +1 -1
  23. omlish/dataclasses/impl/copy.py +30 -0
  24. omlish/dataclasses/impl/exceptions.py +6 -0
  25. omlish/dataclasses/impl/fields.py +25 -25
  26. omlish/dataclasses/impl/init.py +5 -3
  27. omlish/dataclasses/impl/main.py +3 -0
  28. omlish/dataclasses/impl/metaclass.py +6 -1
  29. omlish/dataclasses/impl/order.py +1 -1
  30. omlish/dataclasses/impl/reflect.py +15 -2
  31. omlish/dataclasses/utils.py +44 -0
  32. omlish/defs.py +1 -1
  33. omlish/diag/__init__.py +4 -0
  34. omlish/diag/procfs.py +31 -3
  35. omlish/diag/procstats.py +32 -0
  36. omlish/{testing → diag}/pydevd.py +35 -0
  37. omlish/diag/replserver/console.py +3 -3
  38. omlish/diag/replserver/server.py +6 -5
  39. omlish/diag/threads.py +86 -0
  40. omlish/dispatch/_dispatch2.py +65 -0
  41. omlish/dispatch/_dispatch3.py +104 -0
  42. omlish/docker.py +20 -1
  43. omlish/fnpairs.py +37 -18
  44. omlish/graphs/dags.py +113 -0
  45. omlish/graphs/domination.py +268 -0
  46. omlish/graphs/trees.py +2 -2
  47. omlish/http/__init__.py +25 -0
  48. omlish/http/asgi.py +132 -0
  49. omlish/http/collections.py +15 -0
  50. omlish/http/consts.py +47 -5
  51. omlish/http/cookies.py +194 -0
  52. omlish/http/dates.py +70 -0
  53. omlish/http/encodings.py +6 -0
  54. omlish/http/json.py +273 -0
  55. omlish/http/sessions.py +204 -0
  56. omlish/inject/__init__.py +51 -17
  57. omlish/inject/binder.py +185 -5
  58. omlish/inject/bindings.py +3 -36
  59. omlish/inject/eagers.py +2 -8
  60. omlish/inject/elements.py +30 -9
  61. omlish/inject/exceptions.py +3 -3
  62. omlish/inject/impl/elements.py +65 -31
  63. omlish/inject/impl/injector.py +20 -2
  64. omlish/inject/impl/inspect.py +33 -5
  65. omlish/inject/impl/multis.py +74 -0
  66. omlish/inject/impl/origins.py +75 -0
  67. omlish/inject/impl/{private.py → privates.py} +2 -2
  68. omlish/inject/impl/providers.py +19 -39
  69. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  70. omlish/inject/impl/scopes.py +7 -2
  71. omlish/inject/injector.py +9 -4
  72. omlish/inject/inspect.py +18 -0
  73. omlish/inject/keys.py +11 -23
  74. omlish/inject/listeners.py +26 -0
  75. omlish/inject/managed.py +76 -10
  76. omlish/inject/multis.py +120 -0
  77. omlish/inject/origins.py +27 -0
  78. omlish/inject/overrides.py +5 -4
  79. omlish/inject/{private.py → privates.py} +6 -10
  80. omlish/inject/providers.py +12 -85
  81. omlish/inject/scopes.py +20 -9
  82. omlish/inject/types.py +2 -8
  83. omlish/iterators.py +13 -0
  84. omlish/lang/__init__.py +12 -2
  85. omlish/lang/cached.py +2 -2
  86. omlish/lang/classes/restrict.py +3 -2
  87. omlish/lang/classes/simple.py +18 -8
  88. omlish/lang/classes/virtual.py +2 -2
  89. omlish/lang/contextmanagers.py +75 -2
  90. omlish/lang/datetimes.py +6 -5
  91. omlish/lang/descriptors.py +131 -0
  92. omlish/lang/functions.py +18 -28
  93. omlish/lang/imports.py +11 -2
  94. omlish/lang/iterables.py +20 -1
  95. omlish/lang/typing.py +6 -0
  96. omlish/lifecycles/__init__.py +34 -0
  97. omlish/lifecycles/abstract.py +43 -0
  98. omlish/lifecycles/base.py +51 -0
  99. omlish/lifecycles/contextmanagers.py +74 -0
  100. omlish/lifecycles/controller.py +116 -0
  101. omlish/lifecycles/manager.py +161 -0
  102. omlish/lifecycles/states.py +43 -0
  103. omlish/lifecycles/transitions.py +64 -0
  104. omlish/logs/formatters.py +1 -1
  105. omlish/logs/utils.py +1 -1
  106. omlish/marshal/__init__.py +4 -0
  107. omlish/marshal/datetimes.py +1 -1
  108. omlish/marshal/naming.py +4 -0
  109. omlish/marshal/objects.py +1 -0
  110. omlish/marshal/polymorphism.py +4 -4
  111. omlish/reflect.py +139 -18
  112. omlish/secrets/__init__.py +7 -0
  113. omlish/secrets/marshal.py +41 -0
  114. omlish/secrets/passwords.py +120 -0
  115. omlish/secrets/secrets.py +47 -0
  116. omlish/serde/__init__.py +0 -0
  117. omlish/serde/dotenv.py +574 -0
  118. omlish/{json.py → serde/json.py} +4 -2
  119. omlish/serde/props.py +604 -0
  120. omlish/serde/yaml.py +223 -0
  121. omlish/sql/dbs.py +1 -1
  122. omlish/sql/duckdb.py +136 -0
  123. omlish/sql/sqlean.py +17 -0
  124. omlish/sync.py +70 -0
  125. omlish/term.py +7 -2
  126. omlish/testing/pytest/__init__.py +8 -2
  127. omlish/testing/pytest/helpers.py +0 -24
  128. omlish/testing/pytest/inject/harness.py +4 -4
  129. omlish/testing/pytest/marks.py +45 -0
  130. omlish/testing/pytest/plugins/__init__.py +3 -0
  131. omlish/testing/pytest/plugins/asyncs.py +136 -0
  132. omlish/testing/pytest/plugins/managermarks.py +60 -0
  133. omlish/testing/pytest/plugins/pydevd.py +1 -1
  134. omlish/testing/testing.py +10 -0
  135. omlish/text/delimit.py +4 -0
  136. omlish/text/glyphsplit.py +92 -0
  137. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  138. omlish-0.0.0.dev6.dist-info/RECORD +240 -0
  139. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
  140. omlish/configs/props.py +0 -64
  141. omlish-0.0.0.dev4.dist-info/RECORD +0 -195
  142. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  143. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
omlish/serde/yaml.py ADDED
@@ -0,0 +1,223 @@
1
+ """
2
+ TODO:
3
+ - !! shared 'object format' node hierarchy
4
+ - fuse yaml and hocon - marks, *COMMENTS*, etc
5
+ - goal: perfect rewrites (comments, whitespace)
6
+ - or at least comments
7
+ - rename 'objects'? codecs/serde interplay still unresolved
8
+ """
9
+ import datetime
10
+ import types
11
+ import typing as ta
12
+
13
+ from .. import check
14
+ from .. import dataclasses as dc
15
+ from .. import lang
16
+
17
+
18
+ if ta.TYPE_CHECKING:
19
+ import yaml
20
+ import yaml.nodes as yaml_nodes
21
+ else:
22
+ yaml = lang.proxy_import('yaml')
23
+ yaml_nodes = lang.proxy_import('yaml.nodes')
24
+
25
+
26
+ T = ta.TypeVar('T')
27
+
28
+
29
+ @dc.dataclass(frozen=True)
30
+ class NodeWrapped(lang.Final, ta.Generic[T]):
31
+ value: T
32
+ node: 'yaml_nodes.Node'
33
+
34
+ def __post_init__(self) -> None:
35
+ if isinstance(self.value, NodeWrapped):
36
+ raise TypeError(self.value)
37
+ if not isinstance(self.node, yaml_nodes.Node):
38
+ raise TypeError(self.node)
39
+
40
+
41
+ class NodeUnwrapper:
42
+
43
+ seq_types: tuple[type, ...] = (
44
+ list,
45
+ set,
46
+ tuple,
47
+ )
48
+
49
+ def unwrap_seq(self, nw: NodeWrapped[T]) -> T:
50
+ return nw.value.__class__(map(self.unwrap, nw.value)) # type: ignore
51
+
52
+ map_types: tuple[type, ...] = (
53
+ dict,
54
+ )
55
+
56
+ def unwrap_map(self, nw: NodeWrapped[T]) -> T:
57
+ return nw.value.__class__({self.unwrap(k): self.unwrap(v) for k, v in nw.value.items()}) # type: ignore
58
+
59
+ scalar_types: tuple[type, ...] = (
60
+ bool,
61
+ bytes,
62
+ datetime.datetime,
63
+ float,
64
+ int,
65
+ str,
66
+ type(None),
67
+ )
68
+
69
+ def unwrap_scalar(self, nw: NodeWrapped[T]) -> T:
70
+ return nw.value
71
+
72
+ def unwrap_unknown(self, nw: NodeWrapped[T]) -> T:
73
+ raise TypeError(nw.value)
74
+
75
+ def unwrap(self, nw: NodeWrapped[T]) -> T:
76
+ check.isinstance(nw, NodeWrapped)
77
+ if isinstance(nw.value, self.seq_types):
78
+ return self.unwrap_seq(nw)
79
+ elif isinstance(nw.value, self.map_types):
80
+ return self.unwrap_map(nw)
81
+ elif isinstance(nw.value, self.scalar_types):
82
+ return self.unwrap_scalar(nw)
83
+ else:
84
+ return self.unwrap_unknown(nw)
85
+
86
+
87
+ def unwrap(nw: NodeWrapped[T]) -> T:
88
+ return NodeUnwrapper().unwrap(nw)
89
+
90
+
91
+ class NodeWrappingConstructorMixin:
92
+
93
+ def __init__(self, *args, **kwargs):
94
+ super().__init__(*args, **kwargs)
95
+
96
+ ctors = {
97
+ fn.__name__: fn for fn in [
98
+ self.__class__.construct_yaml_omap,
99
+ self.__class__.construct_yaml_pairs,
100
+ ]
101
+ }
102
+ self.yaml_constructors = {
103
+ tag: ctors.get(ctor.__name__, ctor)
104
+ for tag, ctor in self.yaml_constructors.items() # type: ignore # noqa
105
+ }
106
+
107
+ def construct_object(self, node, deep=False):
108
+ value = super().construct_object(node, deep=deep) # type: ignore
109
+ return NodeWrapped(value, node)
110
+
111
+ def __construct_yaml_pairs(self, node, fn):
112
+ omap = [] # type: ignore
113
+ gen = check.isinstance(fn(node), types.GeneratorType)
114
+ yield omap
115
+ uomap = next(gen)
116
+ lang.exhaust(gen)
117
+ for key, value in uomap: # type: ignore
118
+ omap.append(NodeWrapped((key, value), node))
119
+
120
+ def construct_yaml_omap(self, node):
121
+ return self.__construct_yaml_pairs(node, super().construct_yaml_omap) # type: ignore # noqa
122
+
123
+ def construct_yaml_pairs(self, node):
124
+ return self.__construct_yaml_pairs(node, super().construct_yaml_pairs) # type: ignore # noqa
125
+
126
+
127
+ class _cached_class_property: # noqa
128
+ def __init__(self, fn):
129
+ super().__init__()
130
+ self._fn = fn
131
+ self._attr = None
132
+
133
+ def __call__(self, *args, **kwargs):
134
+ raise TypeError
135
+
136
+ def __set_name__(self, owner, name):
137
+ self._attr = '_' + name
138
+
139
+ def __get__(self, instance, owner):
140
+ if owner is None:
141
+ if instance is None:
142
+ raise RuntimeError
143
+ owner = instance.__class__
144
+ try:
145
+ return owner.__dict__[self._attr]
146
+ except KeyError:
147
+ ret = self._fn(owner)
148
+ setattr(owner, self._attr, ret) # type: ignore
149
+ return ret
150
+
151
+
152
+ class WrappedLoaders(lang.Namespace):
153
+
154
+ @staticmethod
155
+ def _wrap(cls):
156
+ return type('NodeWrapping$' + cls.__name__, (NodeWrappingConstructorMixin, cls), {})
157
+
158
+ Base: type['yaml.BaseLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.BaseLoader)) # type: ignore
159
+
160
+ @classmethod
161
+ def base(cls, *args, **kwargs) -> 'yaml.BaseLoader':
162
+ return cls.Base(*args, **kwargs)
163
+
164
+ Full: type['yaml.FullLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.FullLoader)) # type: ignore
165
+
166
+ @classmethod
167
+ def full(cls, *args, **kwargs) -> 'yaml.FullLoader':
168
+ return cls.Full(*args, **kwargs)
169
+
170
+ Safe: type['yaml.SafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.SafeLoader)) # type: ignore
171
+
172
+ @classmethod
173
+ def safe(cls, *args, **kwargs) -> 'yaml.SafeLoader':
174
+ return cls.Safe(*args, **kwargs)
175
+
176
+ Unsafe: type['yaml.UnsafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.UnsafeLoader)) # type: ignore
177
+
178
+ @classmethod
179
+ def unsafe(cls, *args, **kwargs) -> 'yaml.UnsafeLoader':
180
+ return cls.Unsafe(*args, **kwargs)
181
+
182
+ CBase: type['yaml.CBaseLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CBaseLoader)) # type: ignore
183
+
184
+ @classmethod
185
+ def cbase(cls, *args, **kwargs) -> 'yaml.CBaseLoader':
186
+ return cls.CBase(*args, **kwargs)
187
+
188
+ CFull: type['yaml.CFullLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CFullLoader)) # type: ignore
189
+
190
+ @classmethod
191
+ def cfull(cls, *args, **kwargs) -> 'yaml.CFullLoader':
192
+ return cls.CFull(*args, **kwargs)
193
+
194
+ CSafe: type['yaml.CSafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CSafeLoader)) # type: ignore
195
+
196
+ @classmethod
197
+ def csafe(cls, *args, **kwargs) -> 'yaml.CSafeLoader':
198
+ return cls.CSafe(*args, **kwargs)
199
+
200
+ CUnsafe: type['yaml.CUnsafeLoader'] = _cached_class_property(lambda cls: cls._wrap(yaml.CUnsafeLoader)) # type: ignore # noqa
201
+
202
+ @classmethod
203
+ def cunsafe(cls, *args, **kwargs) -> 'yaml.CUnsafeLoader':
204
+ return cls.CUnsafe(*args, **kwargs)
205
+
206
+
207
+ def load(stream, Loader): # noqa
208
+ with lang.disposing(Loader(stream)) as loader:
209
+ return loader.get_single_data()
210
+
211
+
212
+ def load_all(stream, Loader): # noqa
213
+ with lang.disposing(Loader(stream)) as loader:
214
+ while loader.check_data():
215
+ yield loader.get_data()
216
+
217
+
218
+ def full_load(stream): # noqa
219
+ return load(stream, yaml.FullLoader)
220
+
221
+
222
+ def full_load_all(stream): # noqa # noqa
223
+ return load_all(stream, yaml.FullLoader)
omlish/sql/dbs.py CHANGED
@@ -16,7 +16,7 @@ class DbType:
16
16
  default_port: int | None = None
17
17
 
18
18
 
19
- class DbTypes(lang.Namespace):
19
+ class DbTypes(lang.Namespace, lang.Final):
20
20
  MYSQL = DbType(
21
21
  name='mysql',
22
22
  dialect_name='mysql',
omlish/sql/duckdb.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ See:
3
+ - https://duckdb.org/docs/api/python/overview.html
4
+ - https://github.com/Mause/duckdb_engine/blob/0e3ea0107f81c66d50b444011d31fce22a9b902c/duckdb_engine/__init__.py
5
+ """
6
+ import typing as ta
7
+
8
+ import sqlalchemy as sa
9
+ from sqlalchemy.dialects import postgresql as sap
10
+
11
+ from .. import lang
12
+
13
+
14
+ if ta.TYPE_CHECKING:
15
+ import duckdb
16
+ else:
17
+ duckdb = lang.proxy_import('duckdb')
18
+
19
+
20
+ class ConnectionWrapper:
21
+ def __init__(self, c: 'duckdb.DuckDBPyConnection') -> None:
22
+ super().__init__()
23
+ self.__c = c
24
+ self.autocommit = None
25
+ self.closed = False
26
+
27
+ def cursor(self) -> 'CursorWrapper':
28
+ return CursorWrapper(self.__c, self)
29
+
30
+ def __getattr__(self, name: str) -> ta.Any:
31
+ return getattr(self.__c, name)
32
+
33
+ def close(self) -> None:
34
+ self.__c.close()
35
+ self.closed = True
36
+
37
+
38
+ def _is_transaction_context_message(e: Exception) -> bool:
39
+ return e.args[0] == 'TransactionContext Error: cannot rollback - no transaction is active'
40
+
41
+
42
+ class CursorWrapper:
43
+ def __init__(
44
+ self,
45
+ c: 'duckdb.DuckDBPyConnection',
46
+ connection_wrapper: 'ConnectionWrapper',
47
+ ) -> None:
48
+ super().__init__()
49
+ self.__c = c
50
+ self.__connection_wrapper = connection_wrapper
51
+
52
+ def executemany(
53
+ self,
54
+ statement: str,
55
+ parameters: ta.Sequence[ta.Mapping[str, ta.Any]] | None = None,
56
+ context: ta.Any | None = None,
57
+ ) -> None:
58
+ self.__c.executemany(statement, list(parameters) if parameters else [])
59
+
60
+ def execute(
61
+ self,
62
+ statement: str,
63
+ parameters: ta.Sequence[ta.Any] | None = None,
64
+ context: ta.Any | None = None,
65
+ ) -> None:
66
+ try:
67
+ if statement.lower() == 'commit': # this is largely for ipython-sql
68
+ self.__c.commit()
69
+ elif parameters is None:
70
+ self.__c.execute(statement)
71
+ else:
72
+ self.__c.execute(statement, parameters)
73
+ except RuntimeError as e:
74
+ if e.args[0].startswith('Not implemented Error'):
75
+ raise NotImplementedError(*e.args) from e
76
+ elif _is_transaction_context_message(e):
77
+ return
78
+ else:
79
+ raise
80
+
81
+ @property
82
+ def connection(self):
83
+ return self.__connection_wrapper
84
+
85
+ def close(self) -> None:
86
+ pass
87
+
88
+ def __getattr__(self, name: str) -> ta.Any:
89
+ return getattr(self.__c, name)
90
+
91
+ def fetchmany(self, size: int | None = None) -> list:
92
+ if size is None:
93
+ return self.__c.fetchmany()
94
+ else:
95
+ return self.__c.fetchmany(size)
96
+
97
+
98
+ class DuckdbDialect(sap.base.PGDialect):
99
+ name = 'postgres__duckdb'
100
+ driver = 'duckdb_engine'
101
+
102
+ supports_statement_cache = False
103
+
104
+ @classmethod
105
+ def import_dbapi(cls):
106
+ return duckdb
107
+
108
+ def connect(self, *cargs, **cparams):
109
+ conn = self.loaded_dbapi.connect(*cargs, **cparams)
110
+
111
+ return ConnectionWrapper(conn)
112
+
113
+ def do_rollback(self, connection) -> None:
114
+ try:
115
+ super().do_rollback(connection)
116
+ except duckdb.TransactionException as e:
117
+ if _is_transaction_context_message(e):
118
+ return
119
+ else:
120
+ raise
121
+
122
+ def do_begin(self, connection) -> None:
123
+ connection.begin()
124
+
125
+ def get_default_isolation_level(self, connection) -> ta.Any:
126
+ raise NotImplementedError
127
+
128
+ def _get_server_version_info(self, connection) -> tuple[int, int]:
129
+ connection.execute(sa.text('select version()')).fetchone()
130
+ return (8, 0)
131
+
132
+ def _set_backslash_escapes(self, connection):
133
+ pass
134
+
135
+
136
+ sa.dialects.registry.register(DuckdbDialect.name, __name__, DuckdbDialect.__name__)
omlish/sql/sqlean.py ADDED
@@ -0,0 +1,17 @@
1
+ import sqlalchemy as sa
2
+ from sqlalchemy.dialects import sqlite as sal
3
+
4
+
5
+ class SqleanDialect(sal.dialect): # type: ignore
6
+ name = 'sqlite__sqlean'
7
+ driver = 'sqlean_engine'
8
+
9
+ supports_statement_cache = True
10
+
11
+ @classmethod
12
+ def import_dbapi(cls):
13
+ from sqlean import dbapi2
14
+ return dbapi2
15
+
16
+
17
+ sa.dialects.registry.register(SqleanDialect.name, __name__, SqleanDialect.__name__)
omlish/sync.py ADDED
@@ -0,0 +1,70 @@
1
+ """
2
+ TODO:
3
+ - sync (lol) w/ asyncs.anyio
4
+ - atomics
5
+ """
6
+ import threading
7
+ import typing as ta
8
+
9
+ from . import lang
10
+
11
+
12
+ T = ta.TypeVar('T')
13
+
14
+
15
+ class Once:
16
+ def __init__(self) -> None:
17
+ super().__init__()
18
+ self._done = False
19
+ self._lock = threading.Lock()
20
+
21
+ def do(self, fn: ta.Callable[[], None]) -> bool:
22
+ if self._done:
23
+ return False
24
+ with self._lock:
25
+ if self._done:
26
+ return False # type: ignore
27
+ try:
28
+ fn()
29
+ finally:
30
+ self._done = True
31
+ return True
32
+
33
+
34
+ class Lazy(ta.Generic[T]):
35
+ def __init__(self) -> None:
36
+ super().__init__()
37
+ self._once = Once()
38
+ self._v: lang.Maybe[T] = lang.empty()
39
+
40
+ def peek(self) -> lang.Maybe[T]:
41
+ return self._v
42
+
43
+ def set(self, v: T) -> None:
44
+ self._v = lang.just(v)
45
+
46
+ def get(self, fn: ta.Callable[[], T]) -> T:
47
+ def do():
48
+ self._v = lang.just(fn())
49
+ self._once.do(do)
50
+ return self._v.must()
51
+
52
+
53
+ class LazyFn(ta.Generic[T]):
54
+ def __init__(self, fn: ta.Callable[[], T]) -> None:
55
+ super().__init__()
56
+ self._fn = fn
57
+ self._once = Once()
58
+ self._v: lang.Maybe[T] = lang.empty()
59
+
60
+ def peek(self) -> lang.Maybe[T]:
61
+ return self._v
62
+
63
+ def set(self, v: T) -> None:
64
+ self._v = lang.just(v)
65
+
66
+ def get(self) -> T:
67
+ def do():
68
+ self._v = lang.just(self._fn())
69
+ self._once.do(do)
70
+ return self._v.must()
omlish/term.py CHANGED
@@ -90,7 +90,7 @@ HIDE_CURSOR = ControlSequence(lambda: CSI + '?25l', 'Hide Cursor')
90
90
  SGR = ControlSequence(lambda n: CSI + _str_val(n) + 'm', 'Select Graphic Rendition')
91
91
 
92
92
 
93
- class SGRs(lang.Namespace):
93
+ class SGRs(lang.Namespace, lang.Final):
94
94
  RESET = 0
95
95
  NORMAL_COLOR_AND_INTENSITY = 22
96
96
 
@@ -213,8 +213,13 @@ def main() -> None:
213
213
 
214
214
  sys.stdout.write(SGR(SGRs.RESET))
215
215
  sys.stdout.write(BG8(15) + ' ')
216
- for i in [196, 160, 124, 88, 52, 16]:
216
+ for i in range(20):
217
217
  sys.stdout.write(BG8(i) + ' ')
218
+ sys.stdout.write('\n')
219
+ for i in range(256):
220
+ if i % 12 == 0:
221
+ sys.stdout.write('\n')
222
+ sys.stdout.write(BG8(i + 16) + ' ')
218
223
  sys.stdout.write(SGR(SGRs.RESET) + '\n')
219
224
 
220
225
 
@@ -1,8 +1,14 @@
1
- from . import plugins # noqa
2
-
3
1
  from .helpers import ( # noqa
4
2
  assert_raises_star,
3
+ )
4
+
5
+ from .marks import ( # noqa
6
+ drain_asyncio,
5
7
  skip_if_cant_import,
8
+ skip_if_nogil,
6
9
  skip_if_not_on_path,
7
10
  skip_if_python_version_less_than,
8
11
  )
12
+
13
+ # Imported for convenience in things that import this but not lang.
14
+ from ...lang import breakpoint_on_exception # noqa
@@ -1,28 +1,4 @@
1
1
  import contextlib
2
- import shutil
3
- import sys
4
- import typing as ta
5
-
6
- import pytest
7
-
8
- from ..testing import can_import
9
-
10
-
11
- def skip_if_cant_import(module: str, *args, **kwargs):
12
- return pytest.mark.skipif(not can_import(module, *args, **kwargs), reason=f'requires import {module}')
13
-
14
-
15
- def skip_if_not_on_path(exe: str):
16
- return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
17
-
18
-
19
- def skip_if_python_version_less_than(num: ta.Sequence[int]):
20
- return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
21
-
22
-
23
- def skip_if_not_single():
24
- # [resolve_collection_argument(a) for a in session.config.args]
25
- raise NotImplementedError
26
2
 
27
3
 
28
4
  @contextlib.contextmanager
@@ -4,10 +4,10 @@ import typing as ta
4
4
 
5
5
  import pytest
6
6
 
7
- from .. import plugins
8
7
  from .... import check
9
8
  from .... import inject as inj
10
9
  from .... import lang
10
+ from .. import plugins
11
11
 
12
12
 
13
13
  T = ta.TypeVar('T')
@@ -24,7 +24,7 @@ class PytestScope(enum.Enum):
24
24
  FUNCTION = enum.auto()
25
25
 
26
26
 
27
- class Scopes(lang.Namespace):
27
+ class Scopes(lang.Namespace, lang.Final):
28
28
  Session = inj.SeededScope(PytestScope.SESSION)
29
29
  Package = inj.SeededScope(PytestScope.PACKAGE)
30
30
  Module = inj.SeededScope(PytestScope.MODULE)
@@ -50,7 +50,7 @@ class Harness:
50
50
  super().__init__()
51
51
  self._orig_es = es
52
52
  self._es = inj.as_elements(
53
- inj.as_binding(self),
53
+ inj.bind(self),
54
54
  *[
55
55
  inj.as_elements(
56
56
  inj.bind_scope(ss),
@@ -169,7 +169,7 @@ def bind(
169
169
  pts = scope if isinstance(scope, PytestScope) else PytestScope[check.isinstance(scope, str).upper()]
170
170
  check.isinstance(cls, type)
171
171
  register(inj.as_elements(
172
- inj.in_(cls, _SCOPES_BY_PYTEST_SCOPE[pts]),
172
+ inj.bind(cls, in_=_SCOPES_BY_PYTEST_SCOPE[pts]),
173
173
  ))
174
174
  return cls
175
175
  return inner
@@ -0,0 +1,45 @@
1
+ import shutil
2
+ import sys
3
+ import sysconfig
4
+ import typing as ta
5
+
6
+ import pytest
7
+
8
+ from ... import lang # noqa
9
+ from ..testing import can_import
10
+ from .plugins.managermarks import ManagerMark # noqa
11
+
12
+
13
+ if ta.TYPE_CHECKING:
14
+ from ...asyncs import asyncio as aiu
15
+
16
+ else:
17
+ aiu = lang.proxy_import('...asyncs.asyncio', __package__)
18
+
19
+
20
+ def skip_if_cant_import(module: str, *args, **kwargs):
21
+ return pytest.mark.skipif(not can_import(module, *args, **kwargs), reason=f'requires import {module}')
22
+
23
+
24
+ def skip_if_not_on_path(exe: str):
25
+ return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
26
+
27
+
28
+ def skip_if_python_version_less_than(num: ta.Sequence[int]):
29
+ return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
30
+
31
+
32
+ def skip_if_not_single():
33
+ # FIXME
34
+ # [resolve_collection_argument(a) for a in session.config.args]
35
+ raise NotImplementedError
36
+
37
+
38
+ def skip_if_nogil():
39
+ return pytest.mark.skipif(sysconfig.get_config_var('Py_GIL_DISABLED'), reason='requires gil build')
40
+
41
+
42
+ class drain_asyncio(ManagerMark): # noqa
43
+ def __call__(self, item: pytest.Function) -> ta.Iterator[None]:
44
+ with aiu.draining_asyncio_tasks():
45
+ yield
@@ -1,11 +1,14 @@
1
1
  from . import ( # noqa
2
+ asyncs,
2
3
  logging,
4
+ managermarks,
3
5
  pydevd,
4
6
  repeat,
5
7
  skips,
6
8
  spacing,
7
9
  switches,
8
10
  )
11
+
9
12
  from ._registry import ( # noqa
10
13
  ALL,
11
14
  register,