omlish 0.0.0.dev5__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.
- omlish/__about__.py +1 -1
- omlish/asyncs/__init__.py +9 -0
- omlish/asyncs/anyio.py +83 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/collections/__init__.py +1 -0
- omlish/collections/identity.py +7 -0
- omlish/configs/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/utils.py +44 -0
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +1 -1
- omlish/fnpairs.py +11 -0
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +16 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +45 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +30 -9
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +19 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +75 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +27 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/lang/__init__.py +8 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +2 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +12 -3
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +8 -28
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +5 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/logs/formatters.py +1 -1
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +134 -19
- omlish/secrets/__init__.py +7 -0
- omlish/secrets/marshal.py +41 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +47 -0
- omlish/serde/__init__.py +0 -0
- omlish/{configs → serde}/dotenv.py +12 -24
- omlish/{json.py → serde/json.py} +2 -1
- omlish/serde/yaml.py +223 -0
- omlish/sql/dbs.py +1 -1
- omlish/sql/duckdb.py +136 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +1 -1
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/RECORD +100 -72
- /omlish/{configs → serde}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev5.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
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/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
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from . import plugins # noqa
|
|
2
|
-
|
|
3
1
|
from .helpers import ( # noqa
|
|
4
2
|
assert_raises_star,
|
|
5
3
|
)
|
|
@@ -11,3 +9,6 @@ from .marks import ( # noqa
|
|
|
11
9
|
skip_if_not_on_path,
|
|
12
10
|
skip_if_python_version_less_than,
|
|
13
11
|
)
|
|
12
|
+
|
|
13
|
+
# Imported for convenience in things that import this but not lang.
|
|
14
|
+
from ...lang import breakpoint_on_exception # noqa
|
|
@@ -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.
|
|
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.
|
|
172
|
+
inj.bind(cls, in_=_SCOPES_BY_PYTEST_SCOPE[pts]),
|
|
173
173
|
))
|
|
174
174
|
return cls
|
|
175
175
|
return inner
|
omlish/testing/pytest/marks.py
CHANGED
|
@@ -11,9 +11,10 @@ from .plugins.managermarks import ManagerMark # noqa
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
if ta.TYPE_CHECKING:
|
|
14
|
-
import asyncio
|
|
14
|
+
from ...asyncs import asyncio as aiu
|
|
15
|
+
|
|
15
16
|
else:
|
|
16
|
-
|
|
17
|
+
aiu = lang.proxy_import('...asyncs.asyncio', __package__)
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def skip_if_cant_import(module: str, *args, **kwargs):
|
|
@@ -40,9 +41,5 @@ def skip_if_nogil():
|
|
|
40
41
|
|
|
41
42
|
class drain_asyncio(ManagerMark): # noqa
|
|
42
43
|
def __call__(self, item: pytest.Function) -> ta.Iterator[None]:
|
|
43
|
-
|
|
44
|
-
try:
|
|
44
|
+
with aiu.draining_asyncio_tasks():
|
|
45
45
|
yield
|
|
46
|
-
finally:
|
|
47
|
-
while loop._ready or loop._scheduled: # type: ignore # noqa
|
|
48
|
-
loop._run_once() # type: ignore # noqa
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- auto drain_asyncio
|
|
4
|
+
|
|
5
|
+
==
|
|
6
|
+
|
|
7
|
+
_pytest:
|
|
8
|
+
https://github.com/pytest-dev/pytest/blob/ef9b8f9d748b6f50eab5d43e32d93008f7880899/src/_pytest/python.py#L155
|
|
9
|
+
"async def function and no async plugin installed (see warnings)"
|
|
10
|
+
|
|
11
|
+
pytest_trio:
|
|
12
|
+
https://github.com/python-trio/pytest-trio/blob/f03160aa1dd355a12d39fa21f4aee4e1239efea3/pytest_trio/plugin.py
|
|
13
|
+
- pytest_addoption
|
|
14
|
+
- pytest_configure
|
|
15
|
+
- pytest_collection_modifyitems
|
|
16
|
+
- pytest_runtest_call @(hookwrapper=True)
|
|
17
|
+
- pytest_fixture_setup
|
|
18
|
+
|
|
19
|
+
pytest_asyncio:
|
|
20
|
+
https://github.com/pytest-dev/pytest-asyncio/blob/f45aa18cf3eeeb94075de16a1d797858facab863/pytest_asyncio/plugin.py
|
|
21
|
+
- pytest_addoption
|
|
22
|
+
- pytest_configure
|
|
23
|
+
- pytest_pycollect_makeitem_preprocess_async_fixtures @(specname="pytest_pycollect_makeitem", tryfirst=True)
|
|
24
|
+
- pytest_pycollect_makeitem_convert_async_functions_to_subclass @(specname="pytest_pycollect_makeitem", hookwrapper=True)
|
|
25
|
+
- pytest_generate_tests @(tryfirst=True)
|
|
26
|
+
- pytest_runtest_setup(item: pytest.Item) -> None:
|
|
27
|
+
- pytest_pyfunc_call @(tryfirst=True, hookwrapper=True)
|
|
28
|
+
- pytest_collectstart @()
|
|
29
|
+
- pytest_report_header @(tryfirst=True)
|
|
30
|
+
- pytest_fixture_setup @(hookwrapper=True)
|
|
31
|
+
|
|
32
|
+
anyio.pytest_plugin:
|
|
33
|
+
https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/pytest_plugin.py
|
|
34
|
+
- pytest_configure
|
|
35
|
+
- pytest_pycollect_makeitem @(tryfirst=True)
|
|
36
|
+
- pytest_pyfunc_call @(tryfirst=True)
|
|
37
|
+
- pytest_fixture_setup
|
|
38
|
+
|
|
39
|
+
""" # noqa
|
|
40
|
+
import functools
|
|
41
|
+
import inspect
|
|
42
|
+
import typing as ta
|
|
43
|
+
|
|
44
|
+
import pytest
|
|
45
|
+
|
|
46
|
+
from .... import lang
|
|
47
|
+
from ....asyncs import trio_asyncio as trai
|
|
48
|
+
from ....diag import pydevd as pdu
|
|
49
|
+
from ._registry import register
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if ta.TYPE_CHECKING:
|
|
53
|
+
import trio_asyncio
|
|
54
|
+
else:
|
|
55
|
+
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ASYNCS_MARK = 'asyncs'
|
|
59
|
+
|
|
60
|
+
KNOWN_BACKENDS = (
|
|
61
|
+
'asyncio',
|
|
62
|
+
'trio',
|
|
63
|
+
'trio_asyncio',
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
PARAM_NAME = '__async_backend'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def iscoroutinefunction(func: ta.Any) -> bool:
|
|
70
|
+
return inspect.iscoroutinefunction(func) or getattr(func, '_is_coroutine', False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_async_function(func: ta.Any) -> bool:
|
|
74
|
+
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@register
|
|
78
|
+
class AsyncsPlugin:
|
|
79
|
+
ASYNC_BACKENDS: ta.ClassVar[ta.Sequence[str]] = [
|
|
80
|
+
*[s for s in KNOWN_BACKENDS if lang.can_import(s)],
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
def pytest_configure(self, config):
|
|
84
|
+
config.addinivalue_line('markers', f'{ASYNCS_MARK}: marks for all async backends')
|
|
85
|
+
config.addinivalue_line('markers', 'trio_asyncio: marks for trio_asyncio backend')
|
|
86
|
+
|
|
87
|
+
def pytest_generate_tests(self, metafunc):
|
|
88
|
+
if (m := metafunc.definition.get_closest_marker(ASYNCS_MARK)) is not None:
|
|
89
|
+
if m.args:
|
|
90
|
+
bes = m.args
|
|
91
|
+
else:
|
|
92
|
+
bes = self.ASYNC_BACKENDS
|
|
93
|
+
elif metafunc.definition.get_closest_marker('trio_asyncio') is not None:
|
|
94
|
+
bes = ['trio_asyncio']
|
|
95
|
+
else:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if pdu.is_present():
|
|
99
|
+
pdu.patch_for_trio_asyncio()
|
|
100
|
+
|
|
101
|
+
metafunc.fixturenames.append(PARAM_NAME)
|
|
102
|
+
metafunc.parametrize(PARAM_NAME, bes)
|
|
103
|
+
|
|
104
|
+
for c in metafunc._calls: # noqa
|
|
105
|
+
be = c.params[PARAM_NAME]
|
|
106
|
+
if be == 'trio_asyncio':
|
|
107
|
+
c.marks.extend([
|
|
108
|
+
pytest.mark.trio.mark,
|
|
109
|
+
pytest.mark.trio_asyncio.mark,
|
|
110
|
+
])
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
c.marks.append(getattr(pytest.mark, be).mark)
|
|
114
|
+
|
|
115
|
+
if pdu.is_present():
|
|
116
|
+
c.marks.append(pytest.mark.drain_asyncio.mark)
|
|
117
|
+
|
|
118
|
+
@pytest.hookimpl(hookwrapper=True)
|
|
119
|
+
def pytest_runtest_call(self, item):
|
|
120
|
+
bes = [be for be in self.ASYNC_BACKENDS if item.get_closest_marker(be) is not None]
|
|
121
|
+
if len(bes) > 1 and set(bes) != {'trio', 'trio_asyncio'}:
|
|
122
|
+
raise Exception(f'{item.nodeid}: multiple async backends specified: {bes}')
|
|
123
|
+
elif is_async_function(item.obj) and not bes:
|
|
124
|
+
raise Exception(f'{item.nodeid}: async def function and no async plugin specified')
|
|
125
|
+
|
|
126
|
+
if 'trio_asyncio' in bes:
|
|
127
|
+
obj = item.obj
|
|
128
|
+
|
|
129
|
+
@functools.wraps(obj)
|
|
130
|
+
@trai.with_trio_asyncio_loop(wait=True)
|
|
131
|
+
async def run(*args, **kwargs):
|
|
132
|
+
await trio_asyncio.aio_as_trio(obj)(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
item.obj = run
|
|
135
|
+
|
|
136
|
+
yield
|