omlish 0.0.0.dev420__py3-none-any.whl → 0.0.0.dev422__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.
Files changed (99) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/asyncs/bluelet/core.py +2 -2
  3. omlish/asyncs/bluelet/events.py +4 -3
  4. omlish/asyncs/bluelet/files.py +2 -2
  5. omlish/asyncs/bluelet/sockets.py +2 -2
  6. omlish/collections/frozen.py +1 -1
  7. omlish/collections/kv/base.py +1 -1
  8. omlish/collections/multimaps.py +1 -1
  9. omlish/collections/persistent/treapmap.py +2 -1
  10. omlish/concurrent/threadlets.py +2 -2
  11. omlish/configs/formats.py +5 -4
  12. omlish/configs/processing/flattening.py +2 -1
  13. omlish/configs/processing/rewriting.py +2 -2
  14. omlish/configs/shadow.py +3 -2
  15. omlish/daemons/spawning.py +2 -2
  16. omlish/daemons/targets.py +1 -1
  17. omlish/daemons/waiting.py +2 -1
  18. omlish/dataclasses/impl/generation/base.py +3 -2
  19. omlish/dataclasses/impl/generation/compilation.py +2 -1
  20. omlish/dataclasses/impl/generation/ops.py +2 -2
  21. omlish/dataclasses/impl/generation/processor.py +1 -1
  22. omlish/dataclasses/metaclass/meta.py +1 -0
  23. omlish/dataclasses/tools/static.py +5 -1
  24. omlish/formats/dotenv.py +3 -1
  25. omlish/formats/logfmt.py +111 -0
  26. omlish/formats/yaml.py +1 -1
  27. omlish/funcs/builders.py +2 -1
  28. omlish/funcs/match.py +1 -1
  29. omlish/funcs/pairs.py +41 -23
  30. omlish/funcs/pipes.py +3 -1
  31. omlish/http/asgi.py +2 -1
  32. omlish/http/coro/client/io.py +3 -2
  33. omlish/http/coro/server/server.py +2 -2
  34. omlish/http/handlers.py +2 -1
  35. omlish/http/jwt.py +1 -1
  36. omlish/http/parsing.py +2 -2
  37. omlish/io/compress/base.py +3 -2
  38. omlish/io/coro/readers.py +4 -3
  39. omlish/io/fdio/handlers.py +3 -2
  40. omlish/io/fdio/pollers.py +3 -1
  41. omlish/lang/__init__.py +20 -8
  42. omlish/lang/attrs.py +3 -1
  43. omlish/lang/casing.py +3 -1
  44. omlish/lang/classes/abstract.py +35 -96
  45. omlish/lang/classes/virtual.py +2 -2
  46. omlish/lang/contextmanagers.py +6 -4
  47. omlish/lang/generators.py +2 -2
  48. omlish/lang/iterables.py +6 -6
  49. omlish/lang/objects.py +0 -58
  50. omlish/lang/outcomes.py +3 -1
  51. omlish/lang/typing.py +5 -24
  52. omlish/lite/abstract.py +119 -0
  53. omlish/lite/contextmanagers.py +4 -1
  54. omlish/lite/inject.py +9 -9
  55. omlish/lite/marshal.py +230 -114
  56. omlish/lite/maybes.py +3 -1
  57. omlish/lite/maysync.py +4 -2
  58. omlish/lite/objects.py +81 -0
  59. omlish/lite/reflect.py +0 -15
  60. omlish/lite/timeouts.py +3 -1
  61. omlish/lite/wrappers.py +23 -0
  62. omlish/logs/abc.py +21 -5
  63. omlish/logs/all.py +14 -3
  64. omlish/logs/callers.py +23 -4
  65. omlish/logs/configs.py +13 -10
  66. omlish/logs/levels.py +7 -0
  67. omlish/logs/protocol.py +84 -42
  68. omlish/logs/typed/__init__.py +0 -0
  69. omlish/logs/typed/bindings.py +537 -0
  70. omlish/logs/typed/contexts.py +138 -0
  71. omlish/logs/typed/types.py +484 -0
  72. omlish/logs/typed/values.py +114 -0
  73. omlish/marshal/__init__.py +1 -0
  74. omlish/os/atomics.py +3 -2
  75. omlish/os/deathpacts/base.py +4 -2
  76. omlish/os/forkhooks.py +2 -2
  77. omlish/os/pidfiles/pinning.py +2 -1
  78. omlish/reflect/types.py +4 -3
  79. omlish/secrets/crypto.py +1 -1
  80. omlish/sockets/bind.py +2 -1
  81. omlish/sockets/handlers.py +3 -2
  82. omlish/sockets/server/handlers.py +2 -1
  83. omlish/sockets/server/server.py +4 -3
  84. omlish/sql/api/base.py +2 -2
  85. omlish/subprocesses/asyncs.py +2 -1
  86. omlish/subprocesses/base.py +2 -2
  87. omlish/subprocesses/maysync.py +1 -2
  88. omlish/subprocesses/run.py +2 -1
  89. omlish/subprocesses/sync.py +2 -1
  90. omlish/term/coloring.py +3 -1
  91. omlish/testing/pytest/plugins/asyncs/backends/base.py +3 -1
  92. omlish/testing/unittest/loading.py +2 -2
  93. omlish/text/asdl.py +4 -3
  94. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/METADATA +1 -1
  95. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/RECORD +99 -89
  96. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/WHEEL +0 -0
  97. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/entry_points.txt +0 -0
  98. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/licenses/LICENSE +0 -0
  99. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev420'
2
- __revision__ = '513bbdf392274f50320eb9577bd88bc0a683e1a5'
1
+ __version__ = '0.0.0.dev422'
2
+ __revision__ = '1e4a98d2785b7e51067fc7386f1721d493a6803f'
3
3
 
4
4
 
5
5
  #
@@ -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, abc.ABC): # noqa
55
+ class CoreBlueletEvent(BlueletEvent, Abstract):
56
56
  pass
57
57
 
58
58
 
@@ -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(abc.ABC): # noqa
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, abc.ABC): # noqa
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.
@@ -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, abc.ABC):
22
+ class FileBlueletEvent(BlueletEvent, Abstract):
23
23
  pass
24
24
 
25
25
 
@@ -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, abc.ABC): # noqa
133
+ class SocketBlueletEvent(BlueletEvent, Abstract):
134
134
  pass
135
135
 
136
136
 
@@ -15,7 +15,7 @@ V = ta.TypeVar('V')
15
15
 
16
16
 
17
17
  class Frozen(ta.Hashable, abc.ABC):
18
- pass
18
+ """Explicitly an abc.ABC, not a lang.Abstract, to support virtual subclassing."""
19
19
 
20
20
 
21
21
  class FrozenDict(ta.Mapping[K, V], Frozen):
@@ -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): # noqa
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
@@ -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
- pass
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(abc.ABC, ta.Iterator[tuple[K, V]], ta.Generic[K, V]):
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__(
@@ -19,7 +19,7 @@ else:
19
19
  ##
20
20
 
21
21
 
22
- class Threadlet(abc.ABC):
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(abc.ABC):
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(abc.ABC): # noqa
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(abc.ABC, ta.Generic[ConfigDataT]):
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(abc.ABC, ta.Generic[ConfigDataT]):
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, abc.ABC):
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(abc.ABC, ta.Generic[K]):
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(abc.ABC): # noqa
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(abc.ABC):
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, abc.ABC):
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
@@ -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, abc.ABC):
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, abc.ABC):
53
+ class InProcessSpawner(Spawner, lang.Abstract):
54
54
  pass
55
55
 
56
56
 
omlish/daemons/targets.py CHANGED
@@ -44,7 +44,7 @@ class Target(dc.Case):
44
44
  raise TypeError(obj)
45
45
 
46
46
 
47
- class TargetRunner(abc.ABC):
47
+ class TargetRunner(lang.Abstract):
48
48
  @abc.abstractmethod
49
49
  def run(self) -> None:
50
50
  raise NotImplementedError
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(abc.ABC):
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(abc.ABC): # noqa
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(abc.ABC, ta.Generic[PlanT]):
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(abc.ABC):
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(abc.ABC): # noqa
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(abc.ABC):
63
+ class Mode(lang.Abstract):
64
64
  @abc.abstractmethod
65
65
  def _process(self, gp: 'GeneratorProcessor', cls: type) -> None:
66
66
  raise NotImplementedError
@@ -1,5 +1,6 @@
1
1
  """
2
2
  TODO:
3
+ - DataABCMeta
3
4
  - Rewrite lol
4
5
  - Enum - enforce Abstract or Final
5
6
  """
@@ -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 = lang.is_abstract_class(cls) or abc.ABC in cls.__bases__
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(metaclass=abc.ABCMeta):
57
+ class DotenvAtom(Abstract):
56
58
  def __ne__(self, other: object) -> bool:
57
59
  result = self.__eq__(other)
58
60
  if result is NotImplemented:
@@ -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.exhaust(gen)
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(abc.ABC):
18
+ class FnBuilder(Abstract):
18
19
  @abc.abstractmethod
19
20
  def build_fn(
20
21
  self,
omlish/funcs/match.py CHANGED
@@ -24,7 +24,7 @@ class MatchGuardError(Exception):
24
24
  pass
25
25
 
26
26
 
27
- class MatchFn(abc.ABC, ta.Generic[P, T]):
27
+ class MatchFn(lang.Abstract, ta.Generic[P, T]):
28
28
  @abc.abstractmethod
29
29
  def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
30
30
  raise NotImplementedError