omlish 0.0.0.dev420__py3-none-any.whl → 0.0.0.dev421__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 +2 -2
- omlish/asyncs/bluelet/core.py +2 -2
- omlish/asyncs/bluelet/events.py +4 -3
- omlish/asyncs/bluelet/files.py +2 -2
- omlish/asyncs/bluelet/sockets.py +2 -2
- omlish/collections/frozen.py +1 -1
- omlish/collections/kv/base.py +1 -1
- omlish/collections/multimaps.py +1 -1
- omlish/collections/persistent/treapmap.py +2 -1
- omlish/concurrent/threadlets.py +2 -2
- omlish/configs/formats.py +5 -4
- omlish/configs/processing/flattening.py +2 -1
- omlish/configs/processing/rewriting.py +2 -2
- omlish/configs/shadow.py +3 -2
- omlish/daemons/spawning.py +2 -2
- omlish/daemons/targets.py +1 -1
- omlish/daemons/waiting.py +2 -1
- omlish/dataclasses/impl/generation/base.py +3 -2
- omlish/dataclasses/impl/generation/compilation.py +2 -1
- omlish/dataclasses/impl/generation/ops.py +2 -2
- omlish/dataclasses/impl/generation/processor.py +1 -1
- omlish/dataclasses/metaclass/meta.py +1 -0
- omlish/dataclasses/tools/static.py +5 -1
- omlish/formats/dotenv.py +3 -1
- omlish/formats/logfmt.py +111 -0
- omlish/formats/yaml.py +1 -1
- omlish/funcs/builders.py +2 -1
- omlish/funcs/match.py +1 -1
- omlish/funcs/pairs.py +41 -23
- omlish/funcs/pipes.py +3 -1
- omlish/http/asgi.py +2 -1
- omlish/http/coro/client/io.py +3 -2
- omlish/http/coro/server/server.py +2 -2
- omlish/http/handlers.py +2 -1
- omlish/http/jwt.py +1 -1
- omlish/http/parsing.py +2 -2
- omlish/io/compress/base.py +3 -2
- omlish/io/coro/readers.py +4 -3
- omlish/io/fdio/handlers.py +3 -2
- omlish/io/fdio/pollers.py +3 -1
- omlish/lang/__init__.py +20 -8
- omlish/lang/attrs.py +3 -1
- omlish/lang/casing.py +3 -1
- omlish/lang/classes/abstract.py +35 -96
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +6 -4
- omlish/lang/generators.py +2 -2
- omlish/lang/iterables.py +6 -6
- omlish/lang/objects.py +0 -58
- omlish/lang/outcomes.py +3 -1
- omlish/lang/typing.py +5 -24
- omlish/lite/abstract.py +119 -0
- omlish/lite/contextmanagers.py +4 -1
- omlish/lite/inject.py +9 -9
- omlish/lite/marshal.py +230 -114
- omlish/lite/maybes.py +3 -1
- omlish/lite/maysync.py +4 -2
- omlish/lite/objects.py +81 -0
- omlish/lite/reflect.py +0 -15
- omlish/lite/timeouts.py +3 -1
- omlish/lite/wrappers.py +23 -0
- omlish/logs/abc.py +21 -5
- omlish/logs/all.py +14 -3
- omlish/logs/configs.py +13 -10
- omlish/logs/levels.py +4 -0
- omlish/logs/protocol.py +77 -39
- omlish/marshal/__init__.py +1 -0
- omlish/os/atomics.py +3 -2
- omlish/os/deathpacts/base.py +4 -2
- omlish/os/forkhooks.py +2 -2
- omlish/os/pidfiles/pinning.py +2 -1
- omlish/reflect/types.py +4 -3
- omlish/secrets/crypto.py +1 -1
- omlish/sockets/bind.py +2 -1
- omlish/sockets/handlers.py +3 -2
- omlish/sockets/server/handlers.py +2 -1
- omlish/sockets/server/server.py +4 -3
- omlish/sql/api/base.py +2 -2
- omlish/subprocesses/asyncs.py +2 -1
- omlish/subprocesses/base.py +2 -2
- omlish/subprocesses/maysync.py +1 -2
- omlish/subprocesses/run.py +2 -1
- omlish/subprocesses/sync.py +2 -1
- omlish/term/coloring.py +3 -1
- omlish/testing/pytest/plugins/asyncs/backends/base.py +3 -1
- omlish/testing/unittest/loading.py +2 -2
- omlish/text/asdl.py +4 -3
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/RECORD +93 -88
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev421.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/asyncs/bluelet/core.py
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
-
import abc
|
9
8
|
import dataclasses as dc
|
10
9
|
import time
|
11
10
|
import types
|
12
11
|
import typing as ta
|
13
12
|
|
13
|
+
from ...lite.abstract import Abstract
|
14
14
|
from .events import BlueletEvent
|
15
15
|
from .events import BlueletFuture
|
16
16
|
from .events import WaitableBlueletEvent
|
@@ -52,7 +52,7 @@ class _BlueletAwaitableDriver:
|
|
52
52
|
##
|
53
53
|
|
54
54
|
|
55
|
-
class CoreBlueletEvent(BlueletEvent,
|
55
|
+
class CoreBlueletEvent(BlueletEvent, Abstract):
|
56
56
|
pass
|
57
57
|
|
58
58
|
|
omlish/asyncs/bluelet/events.py
CHANGED
@@ -5,10 +5,11 @@
|
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
-
import abc
|
9
8
|
import dataclasses as dc
|
10
9
|
import typing as ta
|
11
10
|
|
11
|
+
from ...lite.abstract import Abstract
|
12
|
+
|
12
13
|
|
13
14
|
R = ta.TypeVar('R')
|
14
15
|
|
@@ -20,7 +21,7 @@ BlueletWaitable = ta.Union[int, 'BlueletHasFileno'] # ta.TypeAlias
|
|
20
21
|
##
|
21
22
|
|
22
23
|
|
23
|
-
class BlueletEvent(
|
24
|
+
class BlueletEvent(Abstract):
|
24
25
|
"""
|
25
26
|
Just a base class identifying Bluelet events. An event is an object yielded from a Bluelet coro coroutine to
|
26
27
|
suspend operation and communicate with the scheduler.
|
@@ -44,7 +45,7 @@ class BlueletWaitables:
|
|
44
45
|
x: ta.Sequence[BlueletWaitable] = ()
|
45
46
|
|
46
47
|
|
47
|
-
class WaitableBlueletEvent(BlueletEvent,
|
48
|
+
class WaitableBlueletEvent(BlueletEvent, Abstract):
|
48
49
|
"""
|
49
50
|
A waitable event is one encapsulating an action that can be waited for using a select() call. That is, it's an event
|
50
51
|
with an associated file descriptor.
|
omlish/asyncs/bluelet/files.py
CHANGED
@@ -5,10 +5,10 @@
|
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
-
import abc
|
9
8
|
import dataclasses as dc
|
10
9
|
import typing as ta
|
11
10
|
|
11
|
+
from ...lite.abstract import Abstract
|
12
12
|
from .core import DelegationBlueletEvent
|
13
13
|
from .core import ReturnBlueletEvent
|
14
14
|
from .events import BlueletEvent
|
@@ -19,7 +19,7 @@ from .events import WaitableBlueletEvent
|
|
19
19
|
##
|
20
20
|
|
21
21
|
|
22
|
-
class FileBlueletEvent(BlueletEvent,
|
22
|
+
class FileBlueletEvent(BlueletEvent, Abstract):
|
23
23
|
pass
|
24
24
|
|
25
25
|
|
omlish/asyncs/bluelet/sockets.py
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
-
import abc
|
9
8
|
import dataclasses as dc
|
10
9
|
import socket
|
11
10
|
import typing as ta
|
12
11
|
|
12
|
+
from ...lite.abstract import Abstract
|
13
13
|
from .core import BlueletCoro
|
14
14
|
from .core import ReturnBlueletEvent
|
15
15
|
from .core import ValueBlueletEvent
|
@@ -130,7 +130,7 @@ class BlueletConnection:
|
|
130
130
|
##
|
131
131
|
|
132
132
|
|
133
|
-
class SocketBlueletEvent(BlueletEvent,
|
133
|
+
class SocketBlueletEvent(BlueletEvent, Abstract):
|
134
134
|
pass
|
135
135
|
|
136
136
|
|
omlish/collections/frozen.py
CHANGED
omlish/collections/kv/base.py
CHANGED
@@ -45,7 +45,7 @@ class Kv(lang.Abstract, ta.Generic[K, V]):
|
|
45
45
|
raise NotImplementedError
|
46
46
|
|
47
47
|
|
48
|
-
class MutableKv(Kv[K, V], lang.Abstract):
|
48
|
+
class MutableKv(Kv[K, V], lang.Abstract):
|
49
49
|
@abc.abstractmethod
|
50
50
|
def __setitem__(self, k: K, v: V, /) -> None:
|
51
51
|
raise NotImplementedError
|
omlish/collections/multimaps.py
CHANGED
@@ -13,7 +13,7 @@ MV = ta.TypeVar('MV', bound=ta.Iterable)
|
|
13
13
|
|
14
14
|
|
15
15
|
class MultiMap(ta.Mapping[K, MV], abc.ABC, ta.Generic[K, V, MV]):
|
16
|
-
|
16
|
+
"""Explicitly an abc.ABC, not a lang.Abstract, to support virtual subclassing."""
|
17
17
|
|
18
18
|
|
19
19
|
SequenceMultiMap: ta.TypeAlias = MultiMap[K, V, ta.Sequence[V]]
|
@@ -20,6 +20,7 @@ import abc
|
|
20
20
|
import random
|
21
21
|
import typing as ta
|
22
22
|
|
23
|
+
from ... import lang
|
23
24
|
from ..sorted.sorted import SortedItems
|
24
25
|
from . import treap
|
25
26
|
from .persistent import PersistentMap
|
@@ -154,7 +155,7 @@ def new_treap_dict(cmp: ta.Callable[[tuple[K, V], tuple[K, V]], int]) -> Persist
|
|
154
155
|
##
|
155
156
|
|
156
157
|
|
157
|
-
class BaseTreapMapIterator(
|
158
|
+
class BaseTreapMapIterator(lang.Abstract, ta.Iterator[tuple[K, V]], ta.Generic[K, V]):
|
158
159
|
__slots__ = ('_st', '_n')
|
159
160
|
|
160
161
|
def __init__(
|
omlish/concurrent/threadlets.py
CHANGED
@@ -19,7 +19,7 @@ else:
|
|
19
19
|
##
|
20
20
|
|
21
21
|
|
22
|
-
class Threadlet(
|
22
|
+
class Threadlet(lang.Abstract):
|
23
23
|
"""Not safe to identity-key - use `underlying`."""
|
24
24
|
|
25
25
|
def __hash__(self):
|
@@ -52,7 +52,7 @@ class Threadlet(abc.ABC):
|
|
52
52
|
raise NotImplementedError
|
53
53
|
|
54
54
|
|
55
|
-
class Threadlets(
|
55
|
+
class Threadlets(lang.Abstract):
|
56
56
|
@abc.abstractmethod
|
57
57
|
def spawn(self, fn: ta.Callable[[], None]) -> Threadlet:
|
58
58
|
raise NotImplementedError
|
omlish/configs/formats.py
CHANGED
@@ -23,6 +23,7 @@ from ..formats.ini.sections import extract_ini_sections
|
|
23
23
|
from ..formats.ini.sections import render_ini_sections
|
24
24
|
from ..formats.toml.parser import toml_loads
|
25
25
|
from ..formats.toml.writer import TomlWriter
|
26
|
+
from ..lite.abstract import Abstract
|
26
27
|
from ..lite.check import check
|
27
28
|
from ..lite.json import json_dumps_pretty
|
28
29
|
from .types import ConfigMap
|
@@ -36,7 +37,7 @@ ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
|
|
36
37
|
|
37
38
|
|
38
39
|
@dc.dataclass(frozen=True)
|
39
|
-
class ConfigData(
|
40
|
+
class ConfigData(Abstract):
|
40
41
|
@abc.abstractmethod
|
41
42
|
def as_map(self) -> ConfigMap:
|
42
43
|
raise NotImplementedError
|
@@ -45,7 +46,7 @@ class ConfigData(abc.ABC): # noqa
|
|
45
46
|
#
|
46
47
|
|
47
48
|
|
48
|
-
class ConfigLoader(
|
49
|
+
class ConfigLoader(Abstract, ta.Generic[ConfigDataT]):
|
49
50
|
@property
|
50
51
|
def file_exts(self) -> ta.Sequence[str]:
|
51
52
|
return ()
|
@@ -67,7 +68,7 @@ class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
|
|
67
68
|
#
|
68
69
|
|
69
70
|
|
70
|
-
class ConfigRenderer(
|
71
|
+
class ConfigRenderer(Abstract, ta.Generic[ConfigDataT]):
|
71
72
|
@property
|
72
73
|
@abc.abstractmethod
|
73
74
|
def data_cls(self) -> ta.Type[ConfigDataT]:
|
@@ -87,7 +88,7 @@ class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
|
|
87
88
|
|
88
89
|
|
89
90
|
@dc.dataclass(frozen=True)
|
90
|
-
class ObjConfigData(ConfigData,
|
91
|
+
class ObjConfigData(ConfigData, Abstract):
|
91
92
|
obj: ta.Any
|
92
93
|
|
93
94
|
def as_map(self) -> ConfigMap:
|
@@ -3,6 +3,7 @@
|
|
3
3
|
import abc
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
+
from ...lite.abstract import Abstract
|
6
7
|
from ...lite.check import check
|
7
8
|
|
8
9
|
|
@@ -54,7 +55,7 @@ class ConfigFlattening:
|
|
54
55
|
rec([], unflattened)
|
55
56
|
return ret
|
56
57
|
|
57
|
-
class UnflattenNode(
|
58
|
+
class UnflattenNode(Abstract, ta.Generic[K]):
|
58
59
|
@abc.abstractmethod
|
59
60
|
def get(self, key: K) -> ta.Any:
|
60
61
|
raise NotImplementedError
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007 UP045
|
2
2
|
# @omlish-lite
|
3
|
-
import abc
|
4
3
|
import collections.abc
|
5
4
|
import dataclasses as dc
|
6
5
|
import typing as ta
|
7
6
|
|
7
|
+
from ...lite.abstract import Abstract
|
8
8
|
from ...lite.check import check
|
9
9
|
from ...lite.types import BUILTIN_SCALAR_ITERABLE_TYPES
|
10
10
|
|
@@ -26,7 +26,7 @@ class RawConfigMetadata:
|
|
26
26
|
raise TypeError
|
27
27
|
|
28
28
|
|
29
|
-
class ConfigRewriter(
|
29
|
+
class ConfigRewriter(Abstract):
|
30
30
|
@dc.dataclass(frozen=True)
|
31
31
|
class Context(ta.Generic[T]):
|
32
32
|
obj: T
|
omlish/configs/shadow.py
CHANGED
@@ -11,6 +11,7 @@ import abc
|
|
11
11
|
import os.path
|
12
12
|
import typing as ta
|
13
13
|
|
14
|
+
from ..lite.abstract import Abstract
|
14
15
|
from ..lite.check import check
|
15
16
|
from ..os.mangle import mangle_path
|
16
17
|
from .formats import DEFAULT_CONFIG_LOADERS
|
@@ -23,13 +24,13 @@ ShadowConfig = ta.Mapping[str, ta.Any] # ta.TypeAlias
|
|
23
24
|
##
|
24
25
|
|
25
26
|
|
26
|
-
class ShadowConfigs(
|
27
|
+
class ShadowConfigs(Abstract):
|
27
28
|
@abc.abstractmethod
|
28
29
|
def get_shadow_config(self, path: str) -> ta.Optional[ShadowConfig]:
|
29
30
|
raise NotImplementedError
|
30
31
|
|
31
32
|
|
32
|
-
class FileShadowConfigs(ShadowConfigs,
|
33
|
+
class FileShadowConfigs(ShadowConfigs, Abstract):
|
33
34
|
@abc.abstractmethod
|
34
35
|
def get_shadow_config_file_path(self, path: str) -> str:
|
35
36
|
raise NotImplementedError
|
omlish/daemons/spawning.py
CHANGED
@@ -44,13 +44,13 @@ class Spawn(dc.Frozen, final=True):
|
|
44
44
|
inherit_fds: ta.Collection[int] | None = None
|
45
45
|
|
46
46
|
|
47
|
-
class Spawner(lang.ContextManaged,
|
47
|
+
class Spawner(lang.ContextManaged, lang.Abstract):
|
48
48
|
@abc.abstractmethod
|
49
49
|
def spawn(self, spawn: Spawn) -> None:
|
50
50
|
raise NotImplementedError
|
51
51
|
|
52
52
|
|
53
|
-
class InProcessSpawner(Spawner,
|
53
|
+
class InProcessSpawner(Spawner, lang.Abstract):
|
54
54
|
pass
|
55
55
|
|
56
56
|
|
omlish/daemons/targets.py
CHANGED
omlish/daemons/waiting.py
CHANGED
@@ -4,6 +4,7 @@ import socket
|
|
4
4
|
import typing as ta
|
5
5
|
|
6
6
|
from .. import dataclasses as dc
|
7
|
+
from .. import lang
|
7
8
|
|
8
9
|
|
9
10
|
##
|
@@ -13,7 +14,7 @@ class Wait(dc.Case):
|
|
13
14
|
pass
|
14
15
|
|
15
16
|
|
16
|
-
class Waiter(
|
17
|
+
class Waiter(lang.Abstract):
|
17
18
|
@abc.abstractmethod
|
18
19
|
def do_wait(self) -> bool:
|
19
20
|
raise NotImplementedError
|
@@ -2,6 +2,7 @@ import abc
|
|
2
2
|
import dataclasses as dc
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
+
from .... import lang
|
5
6
|
from ..processing.base import ProcessingContext
|
6
7
|
from .ops import Op
|
7
8
|
from .ops import OpRefMap
|
@@ -15,7 +16,7 @@ PlanT = ta.TypeVar('PlanT')
|
|
15
16
|
|
16
17
|
|
17
18
|
@dc.dataclass(frozen=True)
|
18
|
-
class Plan(
|
19
|
+
class Plan(lang.Abstract):
|
19
20
|
pass
|
20
21
|
|
21
22
|
|
@@ -28,7 +29,7 @@ class PlanResult(ta.Generic[PlanT]):
|
|
28
29
|
ref_map: OpRefMap | None = None
|
29
30
|
|
30
31
|
|
31
|
-
class Generator(
|
32
|
+
class Generator(lang.Abstract, ta.Generic[PlanT]):
|
32
33
|
@abc.abstractmethod
|
33
34
|
def plan(self, ctx: ProcessingContext) -> PlanResult[PlanT] | None:
|
34
35
|
raise NotImplementedError
|
@@ -7,6 +7,7 @@ import dataclasses as dc
|
|
7
7
|
import typing as ta
|
8
8
|
|
9
9
|
from .... import check
|
10
|
+
from .... import lang
|
10
11
|
from ..utils import repr_round_trip_value
|
11
12
|
from .globals import FN_GLOBAL_IMPORTS
|
12
13
|
from .globals import FN_GLOBALS
|
@@ -32,7 +33,7 @@ T = ta.TypeVar('T')
|
|
32
33
|
|
33
34
|
|
34
35
|
class OpCompiler:
|
35
|
-
class Style(
|
36
|
+
class Style(lang.Abstract):
|
36
37
|
@abc.abstractmethod
|
37
38
|
def header_lines(self) -> ta.Sequence[str]:
|
38
39
|
raise NotImplementedError
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import abc
|
2
1
|
import dataclasses as dc
|
3
2
|
import typing as ta
|
4
3
|
|
4
|
+
from .... import lang
|
5
5
|
from .globals import FnGlobal
|
6
6
|
from .idents import IDENT_PREFIX
|
7
7
|
|
@@ -50,7 +50,7 @@ IfAttrPresent: ta.TypeAlias = ta.Literal['skip', 'replace', 'error']
|
|
50
50
|
|
51
51
|
|
52
52
|
@dc.dataclass(frozen=True)
|
53
|
-
class Op(
|
53
|
+
class Op(lang.Abstract):
|
54
54
|
pass
|
55
55
|
|
56
56
|
|
@@ -60,7 +60,7 @@ class Codegen(ProcessingOption):
|
|
60
60
|
|
61
61
|
@register_processor_type(priority=ProcessorPriority.GENERATION)
|
62
62
|
class GeneratorProcessor(Processor):
|
63
|
-
class Mode(
|
63
|
+
class Mode(lang.Abstract):
|
64
64
|
@abc.abstractmethod
|
65
65
|
def _process(self, gp: 'GeneratorProcessor', cls: type) -> None:
|
66
66
|
raise NotImplementedError
|
@@ -88,7 +88,11 @@ class Static(lang.Abstract):
|
|
88
88
|
#
|
89
89
|
|
90
90
|
expected_fld_order: ta.Sequence[str] | None = None
|
91
|
-
is_abstract =
|
91
|
+
is_abstract = (
|
92
|
+
lang.is_abstract_class(cls) or
|
93
|
+
abc.ABC in cls.__bases__ or
|
94
|
+
lang.Abstract in cls.__bases__
|
95
|
+
)
|
92
96
|
if not is_abstract:
|
93
97
|
if is_immediate_dataclass(cls):
|
94
98
|
raise TypeError(cls)
|
omlish/formats/dotenv.py
CHANGED
@@ -35,6 +35,8 @@ import shutil
|
|
35
35
|
import tempfile
|
36
36
|
import typing as ta
|
37
37
|
|
38
|
+
from ..lite.abstract import Abstract
|
39
|
+
|
38
40
|
|
39
41
|
##
|
40
42
|
|
@@ -52,7 +54,7 @@ _dotenv_posix_variable_pat: ta.Pattern[str] = re.compile(
|
|
52
54
|
)
|
53
55
|
|
54
56
|
|
55
|
-
class DotenvAtom(
|
57
|
+
class DotenvAtom(Abstract):
|
56
58
|
def __ne__(self, other: object) -> bool:
|
57
59
|
result = self.__eq__(other)
|
58
60
|
if result is NotImplemented:
|
omlish/formats/logfmt.py
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# ruff: noqa: UP006 UP045
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
https://www.brandur.org/logfmt
|
5
|
+
"""
|
6
|
+
import re
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
_LOGFMT_ENCODE_WS_PAT = re.compile(r'[\s"]')
|
14
|
+
|
15
|
+
|
16
|
+
def logfmt_encode(
|
17
|
+
dct: ta.Mapping[str, ta.Optional[ta.Any]],
|
18
|
+
*,
|
19
|
+
value_encoder: ta.Optional[ta.Callable[[ta.Any], ta.Optional[str]]] = None,
|
20
|
+
) -> str:
|
21
|
+
def encode(s: str) -> str:
|
22
|
+
if _LOGFMT_ENCODE_WS_PAT.search(s) is not None:
|
23
|
+
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
|
24
|
+
else:
|
25
|
+
return s
|
26
|
+
|
27
|
+
if value_encoder is None:
|
28
|
+
value_encoder = str
|
29
|
+
|
30
|
+
lst: ta.List[str] = []
|
31
|
+
for k, v in dct.items():
|
32
|
+
ps: ta.List[str]
|
33
|
+
if v is None:
|
34
|
+
continue
|
35
|
+
elif v is True:
|
36
|
+
ps = [k]
|
37
|
+
elif isinstance(v, str):
|
38
|
+
ps = [k, v]
|
39
|
+
elif (v := value_encoder(v)) is None:
|
40
|
+
continue
|
41
|
+
else:
|
42
|
+
ps = [k, v]
|
43
|
+
|
44
|
+
lst.append('='.join(map(encode, ps)))
|
45
|
+
|
46
|
+
return ' '.join(lst)
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
|
51
|
+
|
52
|
+
_LOGFMT_DECODE_PAT_PART = r'[^"=\s]+|("([^\\]|(\\.))*?")'
|
53
|
+
_LOGFMT_DECODE_PAT = re.compile(
|
54
|
+
rf'\s*'
|
55
|
+
rf'(?P<key>{_LOGFMT_DECODE_PAT_PART})'
|
56
|
+
rf'(=(?P<value>{_LOGFMT_DECODE_PAT_PART}))?'
|
57
|
+
rf'\s*',
|
58
|
+
)
|
59
|
+
|
60
|
+
|
61
|
+
def logfmt_decode(
|
62
|
+
line: str,
|
63
|
+
*,
|
64
|
+
value_decoder: ta.Optional[ta.Callable[[str], ta.Optional[ta.Any]]] = None,
|
65
|
+
) -> ta.Dict[str, ta.Any]:
|
66
|
+
def unquote(s: str) -> str:
|
67
|
+
if s.startswith('"'):
|
68
|
+
if len(s) < 2 or not s.endswith('"'):
|
69
|
+
raise ValueError(s)
|
70
|
+
s = s[1:-1].replace('\\"', '"').replace('\\\\', '\\')
|
71
|
+
return s
|
72
|
+
|
73
|
+
if value_decoder is None:
|
74
|
+
def value_decoder(s: str) -> ta.Any:
|
75
|
+
try:
|
76
|
+
return int(s)
|
77
|
+
except ValueError:
|
78
|
+
pass
|
79
|
+
try:
|
80
|
+
return float(s)
|
81
|
+
except ValueError:
|
82
|
+
pass
|
83
|
+
return s
|
84
|
+
|
85
|
+
dct: ta.Dict[str, ta.Any] = {}
|
86
|
+
p = 0
|
87
|
+
while True:
|
88
|
+
m = _LOGFMT_DECODE_PAT.match(line, p)
|
89
|
+
if m is None:
|
90
|
+
break
|
91
|
+
if m.start() != p:
|
92
|
+
raise RuntimeError
|
93
|
+
|
94
|
+
k = unquote(m.group('key'))
|
95
|
+
if k in dct:
|
96
|
+
raise KeyError(k)
|
97
|
+
|
98
|
+
vs = m.group('value')
|
99
|
+
v: ta.Any
|
100
|
+
if vs is None:
|
101
|
+
v = True
|
102
|
+
else:
|
103
|
+
v = value_decoder(unquote(vs))
|
104
|
+
|
105
|
+
dct[k] = v
|
106
|
+
p = m.end()
|
107
|
+
|
108
|
+
if p < len(line):
|
109
|
+
raise ValueError(f'Unconsumed logfmt input: {line[p:]}')
|
110
|
+
|
111
|
+
return dct
|
omlish/formats/yaml.py
CHANGED
@@ -117,7 +117,7 @@ class NodeWrappingConstructorMixin:
|
|
117
117
|
gen = check.isinstance(fn(node), types.GeneratorType)
|
118
118
|
yield omap
|
119
119
|
uomap = next(gen)
|
120
|
-
lang.
|
120
|
+
lang.consume(gen)
|
121
121
|
for key, value in uomap:
|
122
122
|
omap.append(NodeWrapped((key, value), node))
|
123
123
|
|
omlish/funcs/builders.py
CHANGED
@@ -6,6 +6,7 @@ import os.path
|
|
6
6
|
import sys
|
7
7
|
import typing as ta
|
8
8
|
|
9
|
+
from ..lite.abstract import Abstract
|
9
10
|
from ..lite.cached import cached_nullary
|
10
11
|
from ..lite.check import check
|
11
12
|
from ..os.paths import is_path_in_dir
|
@@ -14,7 +15,7 @@ from ..os.paths import is_path_in_dir
|
|
14
15
|
##
|
15
16
|
|
16
17
|
|
17
|
-
class FnBuilder(
|
18
|
+
class FnBuilder(Abstract):
|
18
19
|
@abc.abstractmethod
|
19
20
|
def build_fn(
|
20
21
|
self,
|
omlish/funcs/match.py
CHANGED