omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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 (163) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +9 -9
  4. omlish/asyncs/anyio.py +123 -19
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/trio_asyncio.py +7 -3
  9. omlish/bootstrap.py +737 -0
  10. omlish/check.py +1 -1
  11. omlish/collections/__init__.py +5 -0
  12. omlish/collections/exceptions.py +2 -0
  13. omlish/collections/identity.py +7 -0
  14. omlish/collections/utils.py +38 -9
  15. omlish/configs/strings.py +96 -0
  16. omlish/dataclasses/__init__.py +16 -0
  17. omlish/dataclasses/impl/copy.py +30 -0
  18. omlish/dataclasses/impl/descriptors.py +95 -0
  19. omlish/dataclasses/impl/exceptions.py +6 -0
  20. omlish/dataclasses/impl/fields.py +24 -25
  21. omlish/dataclasses/impl/init.py +4 -2
  22. omlish/dataclasses/impl/main.py +2 -0
  23. omlish/dataclasses/impl/reflect.py +1 -1
  24. omlish/dataclasses/utils.py +67 -0
  25. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  26. omlish/diag/__init__.py +4 -0
  27. omlish/diag/procfs.py +2 -2
  28. omlish/{testing → diag}/pydevd.py +35 -0
  29. omlish/diag/threads.py +131 -48
  30. omlish/dispatch/_dispatch2.py +65 -0
  31. omlish/dispatch/_dispatch3.py +104 -0
  32. omlish/docker.py +16 -1
  33. omlish/fnpairs.py +11 -4
  34. omlish/formats/__init__.py +0 -0
  35. omlish/{configs → formats}/dotenv.py +15 -24
  36. omlish/{json.py → formats/json.py} +2 -1
  37. omlish/formats/yaml.py +223 -0
  38. omlish/graphs/trees.py +1 -1
  39. omlish/http/asgi.py +2 -1
  40. omlish/http/collections.py +15 -0
  41. omlish/http/consts.py +22 -1
  42. omlish/http/sessions.py +10 -3
  43. omlish/inject/__init__.py +49 -17
  44. omlish/inject/binder.py +185 -5
  45. omlish/inject/bindings.py +3 -36
  46. omlish/inject/eagers.py +2 -8
  47. omlish/inject/elements.py +31 -10
  48. omlish/inject/exceptions.py +1 -1
  49. omlish/inject/impl/elements.py +37 -12
  50. omlish/inject/impl/injector.py +72 -25
  51. omlish/inject/impl/inspect.py +33 -5
  52. omlish/inject/impl/origins.py +77 -0
  53. omlish/inject/impl/{private.py → privates.py} +2 -2
  54. omlish/inject/impl/scopes.py +6 -2
  55. omlish/inject/injector.py +8 -4
  56. omlish/inject/inspect.py +18 -0
  57. omlish/inject/keys.py +8 -14
  58. omlish/inject/listeners.py +26 -0
  59. omlish/inject/managed.py +76 -10
  60. omlish/inject/multis.py +68 -18
  61. omlish/inject/origins.py +30 -0
  62. omlish/inject/overrides.py +5 -4
  63. omlish/inject/{private.py → privates.py} +6 -10
  64. omlish/inject/providers.py +12 -85
  65. omlish/inject/scopes.py +13 -6
  66. omlish/inject/types.py +3 -1
  67. omlish/inject/utils.py +18 -0
  68. omlish/iterators.py +69 -2
  69. omlish/lang/__init__.py +24 -9
  70. omlish/lang/cached.py +2 -2
  71. omlish/lang/classes/restrict.py +12 -1
  72. omlish/lang/classes/simple.py +18 -8
  73. omlish/lang/contextmanagers.py +13 -4
  74. omlish/lang/descriptors.py +132 -1
  75. omlish/lang/functions.py +8 -28
  76. omlish/lang/imports.py +67 -0
  77. omlish/lang/iterables.py +60 -1
  78. omlish/lang/maybes.py +3 -0
  79. omlish/lang/objects.py +38 -0
  80. omlish/lang/strings.py +25 -0
  81. omlish/lang/sys.py +9 -0
  82. omlish/lang/typing.py +42 -0
  83. omlish/lifecycles/__init__.py +34 -0
  84. omlish/lifecycles/abstract.py +43 -0
  85. omlish/lifecycles/base.py +51 -0
  86. omlish/lifecycles/contextmanagers.py +74 -0
  87. omlish/lifecycles/controller.py +116 -0
  88. omlish/lifecycles/manager.py +161 -0
  89. omlish/lifecycles/states.py +43 -0
  90. omlish/lifecycles/transitions.py +64 -0
  91. omlish/lite/__init__.py +1 -0
  92. omlish/lite/cached.py +18 -0
  93. omlish/lite/check.py +29 -0
  94. omlish/lite/contextmanagers.py +18 -0
  95. omlish/lite/json.py +30 -0
  96. omlish/lite/logs.py +52 -0
  97. omlish/lite/marshal.py +316 -0
  98. omlish/lite/reflect.py +49 -0
  99. omlish/lite/runtime.py +18 -0
  100. omlish/lite/secrets.py +19 -0
  101. omlish/lite/strings.py +25 -0
  102. omlish/lite/subprocesses.py +112 -0
  103. omlish/logs/configs.py +15 -2
  104. omlish/logs/formatters.py +7 -2
  105. omlish/marshal/__init__.py +32 -0
  106. omlish/marshal/any.py +5 -5
  107. omlish/marshal/base.py +27 -11
  108. omlish/marshal/base64.py +24 -9
  109. omlish/marshal/dataclasses.py +34 -28
  110. omlish/marshal/datetimes.py +74 -18
  111. omlish/marshal/enums.py +14 -8
  112. omlish/marshal/exceptions.py +11 -1
  113. omlish/marshal/factories.py +59 -74
  114. omlish/marshal/forbidden.py +35 -0
  115. omlish/marshal/global_.py +11 -4
  116. omlish/marshal/iterables.py +21 -24
  117. omlish/marshal/mappings.py +23 -26
  118. omlish/marshal/naming.py +4 -0
  119. omlish/marshal/numbers.py +51 -0
  120. omlish/marshal/objects.py +1 -0
  121. omlish/marshal/optionals.py +11 -12
  122. omlish/marshal/polymorphism.py +86 -21
  123. omlish/marshal/primitives.py +4 -5
  124. omlish/marshal/standard.py +13 -8
  125. omlish/marshal/uuids.py +4 -5
  126. omlish/matchfns.py +218 -0
  127. omlish/os.py +64 -0
  128. omlish/reflect/__init__.py +39 -0
  129. omlish/reflect/isinstance.py +38 -0
  130. omlish/reflect/ops.py +84 -0
  131. omlish/reflect/subst.py +110 -0
  132. omlish/reflect/types.py +275 -0
  133. omlish/secrets/__init__.py +23 -0
  134. omlish/secrets/crypto.py +132 -0
  135. omlish/secrets/marshal.py +70 -0
  136. omlish/secrets/openssl.py +207 -0
  137. omlish/secrets/passwords.py +120 -0
  138. omlish/secrets/secrets.py +299 -0
  139. omlish/secrets/subprocesses.py +42 -0
  140. omlish/sql/dbs.py +7 -6
  141. omlish/sql/duckdb.py +136 -0
  142. omlish/sql/exprs.py +12 -0
  143. omlish/sql/secrets.py +10 -0
  144. omlish/sql/sqlean.py +17 -0
  145. omlish/term.py +2 -2
  146. omlish/testing/pytest/__init__.py +3 -2
  147. omlish/testing/pytest/inject/harness.py +3 -3
  148. omlish/testing/pytest/marks.py +4 -7
  149. omlish/testing/pytest/plugins/__init__.py +1 -0
  150. omlish/testing/pytest/plugins/asyncs.py +136 -0
  151. omlish/testing/pytest/plugins/pydevd.py +1 -1
  152. omlish/testing/pytest/plugins/switches.py +54 -19
  153. omlish/text/glyphsplit.py +97 -0
  154. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  155. omlish-0.0.0.dev7.dist-info/RECORD +268 -0
  156. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  157. omlish/reflect.py +0 -355
  158. omlish-0.0.0.dev5.dist-info/METADATA +0 -34
  159. omlish-0.0.0.dev5.dist-info/RECORD +0 -212
  160. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  161. /omlish/{configs → formats}/props.py +0 -0
  162. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  163. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
@@ -32,6 +32,7 @@ import os
32
32
  import sys
33
33
  import tempfile
34
34
  import textwrap
35
+ import threading
35
36
  import types
36
37
  import typing as ta
37
38
 
@@ -243,3 +244,37 @@ def maybe_reexec(
243
244
  args = [args[0], bootstrap_path]
244
245
 
245
246
  os.execvp(sys.executable, args)
247
+
248
+
249
+ def debug_unhandled_exception(exc_info: ta.Any = None) -> None:
250
+ if exc_info is None:
251
+ exc_info = sys.exc_info()
252
+
253
+ try:
254
+ import pydevd
255
+ from pydevd import pydevd_tracing
256
+
257
+ except ImportError:
258
+ return
259
+
260
+ exctype, value, traceback = exc_info
261
+ frames = []
262
+ while traceback:
263
+ frames.append(traceback.tb_frame)
264
+ traceback = traceback.tb_next
265
+
266
+ thread = threading.current_thread()
267
+ frames_by_id = {id(frame): frame for frame in frames}
268
+ frame = frames[-1]
269
+ exception = (exctype, value, traceback)
270
+
271
+ if hasattr(thread, 'additional_info'):
272
+ thread.additional_info.pydev_message = 'server exception'
273
+ try:
274
+ debugger = pydevd.debugger # noqa
275
+ except AttributeError:
276
+ debugger = pydevd.get_global_debugger() # noqa
277
+
278
+ pydevd_tracing.SetTrace(None)
279
+
280
+ debugger.stop_on_unhandled_exception(thread, frame, frames_by_id, exception)
omlish/diag/threads.py CHANGED
@@ -9,6 +9,90 @@ import traceback
9
9
  import typing as ta
10
10
 
11
11
 
12
+ ##
13
+
14
+
15
+ def dump_threads(out: ta.IO) -> None:
16
+ out.write('\n\n')
17
+
18
+ thrs_by_tid = {t.ident: t for t in threading.enumerate()}
19
+
20
+ for tid, fr in sys._current_frames().items(): # noqa
21
+ try:
22
+ thr = thrs_by_tid[tid]
23
+ except KeyError:
24
+ thr_rpr = repr(tid)
25
+ else:
26
+ thr_rpr = repr(thr)
27
+
28
+ tb = traceback.format_stack(fr)
29
+
30
+ out.write(f'{thr_rpr}\n')
31
+ out.write('\n'.join(l.strip() for l in tb))
32
+ out.write('\n\n')
33
+
34
+
35
+ def dump_threads_str() -> str:
36
+ out = io.StringIO()
37
+ dump_threads(out)
38
+ return out.getvalue()
39
+
40
+
41
+ ##
42
+
43
+
44
+ class StoppableThread:
45
+ def __init__(
46
+ self,
47
+ fn: ta.Callable[[], None],
48
+ interval_s: float,
49
+ *,
50
+ tick_immediately: bool = False,
51
+ start: bool = False,
52
+ **kwargs: ta.Any,
53
+ ) -> None:
54
+ super().__init__()
55
+ self._fn = fn
56
+ self._interval_s = interval_s
57
+ self._tick_immediately = tick_immediately
58
+ self._thread = threading.Thread(target=self._loop, **kwargs)
59
+ self._stop_event = threading.Event()
60
+ if start:
61
+ self.start()
62
+
63
+ @property
64
+ def thread(self) -> threading.Thread:
65
+ return self._thread
66
+
67
+ @property
68
+ def ident(self) -> int | None:
69
+ return self._thread.ident
70
+
71
+ def start(self) -> None:
72
+ return self._thread.start()
73
+
74
+ def stop_nowait(self) -> None:
75
+ self._stop_event.set()
76
+
77
+ def stop_wait(self, timeout: float | None = None) -> None:
78
+ self.stop_nowait()
79
+ self._thread.join(timeout)
80
+
81
+ def _loop(self) -> None:
82
+ if self._tick_immediately:
83
+ self._fn()
84
+
85
+ while True:
86
+ self._stop_event.wait(self._interval_s)
87
+ if self._stop_event.is_set():
88
+ return
89
+
90
+ self._fn()
91
+
92
+
93
+ ##
94
+
95
+
12
96
  _DEBUG_THREAD_COUNTER = itertools.count()
13
97
 
14
98
 
@@ -18,47 +102,23 @@ def create_thread_dump_thread(
18
102
  out: ta.TextIO = sys.stderr,
19
103
  start: bool = False,
20
104
  nodaemon: bool = False,
21
- ) -> threading.Thread:
22
- def dump():
23
- cthr = threading.current_thread()
24
- thrs_by_tid = {t.ident: t for t in threading.enumerate()}
25
-
26
- buf = io.StringIO()
27
- for tid, fr in sys._current_frames().items(): # noqa
28
- if tid == cthr.ident:
29
- continue
30
-
31
- try:
32
- thr = thrs_by_tid[tid]
33
- except KeyError:
34
- thr_rpr = repr(tid)
35
- else:
36
- thr_rpr = repr(thr)
37
-
38
- tb = traceback.format_stack(fr)
39
-
40
- buf.write(f'{thr_rpr}\n')
41
- buf.write('\n'.join(l.strip() for l in tb))
42
- buf.write('\n\n')
105
+ ) -> StoppableThread:
106
+ def proc() -> None:
107
+ try:
108
+ out.write(dump_threads_str())
109
+ except Exception as e: # noqa
110
+ out.write(repr(e) + '\n\n')
43
111
 
44
- out.write(buf.getvalue())
45
-
46
- def proc():
47
- while True:
48
- time.sleep(interval_s)
49
- try:
50
- dump()
51
- except Exception as e: # noqa
52
- out.write(repr(e) + '\n\n')
53
-
54
- dthr = threading.Thread(
55
- target=proc,
112
+ return StoppableThread(
113
+ proc,
114
+ interval_s,
56
115
  daemon=not nodaemon,
57
116
  name=f'thread-dump-thread-{next(_DEBUG_THREAD_COUNTER)}',
117
+ start=start,
58
118
  )
59
- if start:
60
- dthr.start()
61
- return dthr
119
+
120
+
121
+ ##
62
122
 
63
123
 
64
124
  def create_suicide_thread(
@@ -67,20 +127,43 @@ def create_suicide_thread(
67
127
  interval_s: float = 1.,
68
128
  parent_thread: threading.Thread | None = None,
69
129
  start: bool = False,
70
- ) -> threading.Thread:
130
+ ) -> StoppableThread:
131
+ """Kills process when parent_thread dies."""
132
+
71
133
  if parent_thread is None:
72
134
  parent_thread = threading.current_thread()
73
135
 
74
- def proc():
75
- while True:
76
- parent_thread.join(interval_s)
77
- if not parent_thread.is_alive():
78
- os.kill(os.getpid(), sig)
136
+ def proc() -> None:
137
+ if not parent_thread.is_alive():
138
+ os.kill(os.getpid(), sig)
79
139
 
80
- dthr = threading.Thread(
81
- target=proc,
140
+ return StoppableThread(
141
+ proc,
142
+ interval_s,
82
143
  name=f'suicide-thread-{next(_DEBUG_THREAD_COUNTER)}',
144
+ start=start,
145
+ )
146
+
147
+
148
+ ##
149
+
150
+
151
+ def create_timebomb_thread(
152
+ delay_s: float,
153
+ *,
154
+ sig: int = signal.SIGKILL,
155
+ interval_s: float = 1.,
156
+ start: bool = False,
157
+ ) -> StoppableThread:
158
+ def proc() -> None:
159
+ if time.time() >= deadline:
160
+ os.kill(os.getpid(), sig)
161
+
162
+ deadline = time.time() + delay_s
163
+
164
+ return StoppableThread(
165
+ proc,
166
+ interval_s,
167
+ name=f'timebomb-thread-{next(_DEBUG_THREAD_COUNTER)}',
168
+ start=start,
83
169
  )
84
- if start:
85
- dthr.start()
86
- return dthr
@@ -0,0 +1,65 @@
1
+ import abc
2
+ import typing as ta
3
+ import weakref
4
+
5
+ from .dispatch import find_impl
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+
10
+
11
+ ##
12
+
13
+
14
+ class Dispatcher(ta.Generic[T]):
15
+ def __init__(self) -> None:
16
+ super().__init__()
17
+
18
+ self._impls_by_arg_cls: dict[type, T] = {}
19
+ self._dispatch_cache: dict[ta.Any, T | None] = {}
20
+
21
+ def cache_remove(k, self_ref=weakref.ref(self)):
22
+ if (ref_self := self_ref()) is not None:
23
+ cache = ref_self._dispatch_cache # noqa
24
+ try:
25
+ del cache[k]
26
+ except KeyError:
27
+ pass
28
+
29
+ self._cache_remove = cache_remove
30
+
31
+ self._cache_token: ta.Any = None
32
+
33
+ def cache_size(self) -> int:
34
+ return len(self._dispatch_cache)
35
+
36
+ def register(self, impl: T, cls_col: ta.Iterable[type]) -> T:
37
+ for cls in cls_col:
38
+ self._impls_by_arg_cls[cls] = impl
39
+
40
+ if self._cache_token is None and hasattr(cls, '__abstractmethods__'):
41
+ self._cache_token = abc.get_cache_token()
42
+
43
+ self._dispatch_cache.clear()
44
+ return impl
45
+
46
+ def dispatch(self, cls: type) -> T | None:
47
+ if self._cache_token is not None and (current_token := abc.get_cache_token()) != self._cache_token:
48
+ self._dispatch_cache.clear()
49
+ self._cache_token = current_token
50
+
51
+ cls_ref = weakref.ref(cls)
52
+ try:
53
+ return self._dispatch_cache[cls_ref]
54
+ except KeyError:
55
+ pass
56
+ del cls_ref
57
+
58
+ impl: T | None
59
+ try:
60
+ impl = self._impls_by_arg_cls[cls]
61
+ except KeyError:
62
+ impl = find_impl(cls, self._impls_by_arg_cls)
63
+
64
+ self._dispatch_cache[weakref.ref(cls, self._cache_remove)] = impl
65
+ return impl
@@ -0,0 +1,104 @@
1
+ import abc
2
+ import typing as ta
3
+ import weakref
4
+
5
+ from .dispatch import find_impl
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+
10
+
11
+ ##
12
+
13
+
14
+ class DispatchCacheProtocol(ta.Protocol[T]):
15
+ def size(self) -> int: ...
16
+ def prepare(self, cls: type) -> None: ...
17
+ def clear(self) -> None: ...
18
+ def put(self, cls: type, impl: T) -> None: ...
19
+ def get(self, cls: type) -> T: ... # Raises[KeyError]
20
+
21
+
22
+ class DispatcherProtocol(ta.Protocol[T]):
23
+ def cache_size(self) -> int: ...
24
+ def register(self, impl: T, cls_col: ta.Iterable[type]) -> T: ...
25
+ def dispatch(self, cls: type) -> T | None: ...
26
+
27
+
28
+ ##
29
+
30
+
31
+ class DispatchCache(DispatchCacheProtocol[T]):
32
+ def __init__(self) -> None:
33
+ super().__init__()
34
+
35
+ self._dct: dict[ta.Any, T] = {}
36
+
37
+ def remove(k, self_ref=weakref.ref(self)):
38
+ if (ref_self := self_ref()) is not None:
39
+ dct = ref_self._dct # noqa
40
+ try:
41
+ del dct[k]
42
+ except KeyError:
43
+ pass
44
+
45
+ self._remove = remove
46
+
47
+ self._token: ta.Any = None
48
+
49
+ def size(self) -> int:
50
+ return len(self._dct)
51
+
52
+ def prepare(self, cls: type) -> None:
53
+ if self._token is None and hasattr(cls, '__abstractmethods__'):
54
+ self._token = abc.get_cache_token()
55
+
56
+ self.clear()
57
+
58
+ def clear(self) -> None:
59
+ if self._dct:
60
+ self._dct.clear()
61
+
62
+ def put(self, cls: type, impl: T) -> None:
63
+ self._dct[weakref.ref(cls, self._remove)] = impl
64
+
65
+ def get(self, cls: type) -> T:
66
+ if self._token is not None and (current_token := abc.get_cache_token()) != self._token:
67
+ self._dct.clear()
68
+ self._token = current_token
69
+
70
+ cls_ref = weakref.ref(cls)
71
+ return self._dct[cls_ref]
72
+
73
+
74
+ class Dispatcher(DispatcherProtocol[T]):
75
+ def __init__(self) -> None:
76
+ super().__init__()
77
+
78
+ self._impls_by_arg_cls: dict[type, T] = {}
79
+ self._cache: DispatchCache[T | None] = DispatchCache()
80
+
81
+ def cache_size(self) -> int:
82
+ return self._cache.size()
83
+
84
+ def register(self, impl: T, cls_col: ta.Iterable[type]) -> T:
85
+ for cls in cls_col:
86
+ self._impls_by_arg_cls[cls] = impl
87
+ self._cache.prepare(cls)
88
+
89
+ return impl
90
+
91
+ def dispatch(self, cls: type) -> T | None:
92
+ try:
93
+ return self._cache.get(cls)
94
+ except KeyError:
95
+ pass
96
+
97
+ impl: T | None
98
+ try:
99
+ impl = self._impls_by_arg_cls[cls]
100
+ except KeyError:
101
+ impl = find_impl(cls, self._impls_by_arg_cls)
102
+
103
+ self._cache.put(cls, impl)
104
+ return impl
omlish/docker.py CHANGED
@@ -18,13 +18,14 @@ import datetime
18
18
  import re
19
19
  import shlex
20
20
  import subprocess
21
+ import sys
21
22
  import typing as ta
22
23
 
23
24
  from . import check
24
25
  from . import dataclasses as dc
25
- from . import json
26
26
  from . import lang
27
27
  from . import marshal as msh
28
+ from .formats import json
28
29
 
29
30
 
30
31
  if ta.TYPE_CHECKING:
@@ -168,3 +169,17 @@ def timebomb_payload(delay_s: float, name: str = 'omlish-docker-timebomb') -> st
168
169
  'sh -c \'killall5 -9 -o $PPID -o $$ ; kill 1\''
169
170
  ') &'
170
171
  )
172
+
173
+
174
+ ##
175
+
176
+
177
+ _LIKELY_IN_DOCKER_PATTERN = re.compile(r'^overlay / .*/docker/')
178
+
179
+
180
+ def is_likely_in_docker() -> bool:
181
+ if sys.platform != 'linux':
182
+ return False
183
+ with open('/proc/mounts') as f: # type: ignore
184
+ ls = f.readlines()
185
+ return any(_LIKELY_IN_DOCKER_PATTERN.match(l) for l in ls)
omlish/fnpairs.py CHANGED
@@ -4,11 +4,7 @@ TODO:
4
4
  - csv
5
5
  - csvloader
6
6
  - cbor
7
- - cloudpickle
8
7
  - alt json backends
9
- - compression
10
- - snappy
11
- - lz4
12
8
  - wrapped (wait for usecase)
13
9
  """
14
10
  import abc
@@ -29,6 +25,7 @@ if ta.TYPE_CHECKING:
29
25
  import tomllib as _tomllib
30
26
 
31
27
  import cloudpickle as _cloudpickle
28
+ import json5 as _json5
32
29
  import lz4.frame as _lz4_frame
33
30
  import snappy as _snappy
34
31
  import yaml as _yaml
@@ -44,6 +41,7 @@ else:
44
41
  _tomllib = lang.proxy_import('tomllib')
45
42
 
46
43
  _cloudpickle = lang.proxy_import('cloudpickle')
44
+ _json5 = lang.proxy_import('json5')
47
45
  _lz4_frame = lang.proxy_import('lz4.frame')
48
46
  _snappy = lang.proxy_import('snappy')
49
47
  _yaml = lang.proxy_import('yaml')
@@ -391,6 +389,15 @@ class Cloudpickle(ObjectBytes_):
391
389
  return _cloudpickle.loads(t)
392
390
 
393
391
 
392
+ @_register_extension('json5')
393
+ class Json5(ObjectStr_):
394
+ def forward(self, f: ta.Any) -> str:
395
+ return _json5.dumps(f)
396
+
397
+ def backward(self, t: str) -> ta.Any:
398
+ return _json5.loads(t)
399
+
400
+
394
401
  @_register_extension('yml', 'yaml')
395
402
  class Yaml(ObjectStr_):
396
403
  def forward(self, f: ta.Any) -> str:
File without changes
@@ -20,6 +20,7 @@
20
20
  # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21
21
  # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22
22
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ # https://github.com/theskumar/python-dotenv/tree/4d505f2c9bc3569791e64bca0f2e4300f43df0e0/src/dotenv
23
24
  import abc
24
25
  import codecs
25
26
  import contextlib
@@ -332,6 +333,7 @@ class DotEnv:
332
333
  encoding: str | None = None,
333
334
  interpolate: bool = True,
334
335
  override: bool = True,
336
+ env: ta.Mapping[str, str] | None = None,
335
337
  ) -> None:
336
338
  super().__init__()
337
339
  self.path: StrPath | None = path
@@ -341,6 +343,7 @@ class DotEnv:
341
343
  self.encoding: str | None = encoding
342
344
  self.interpolate: bool = interpolate
343
345
  self.override: bool = override
346
+ self.env = env or {}
344
347
 
345
348
  @contextlib.contextmanager
346
349
  def _get_stream(self) -> ta.Iterator[ta.IO[str]]:
@@ -364,7 +367,7 @@ class DotEnv:
364
367
  raw_values = self.parse()
365
368
 
366
369
  if self.interpolate:
367
- self._dict = resolve_variables(raw_values, override=self.override)
370
+ self._dict = resolve_variables(raw_values, override=self.override, env=self.env)
368
371
  else:
369
372
  self._dict = dict(raw_values)
370
373
 
@@ -376,22 +379,7 @@ class DotEnv:
376
379
  if mapping.key is not None:
377
380
  yield mapping.key, mapping.value
378
381
 
379
- def set_as_environment_variables(self) -> bool:
380
- """Load the current dotenv as system environment variable."""
381
- if not self.dict():
382
- return False
383
-
384
- for k, v in self.dict().items():
385
- if k in os.environ and not self.override:
386
- continue
387
- if v is not None:
388
- os.environ[k] = v
389
-
390
- return True
391
-
392
382
  def get(self, key: str) -> str | None:
393
- """
394
- """
395
383
  data = self.dict()
396
384
 
397
385
  if key in data:
@@ -525,8 +513,9 @@ def unset_key(
525
513
 
526
514
 
527
515
  def resolve_variables(
528
- values: ta.Iterable[tuple[str, str | None]],
529
- override: bool,
516
+ values: ta.Iterable[tuple[str, str | None]],
517
+ override: bool,
518
+ env: ta.Mapping[str, str],
530
519
  ) -> dict[str, str | None]:
531
520
  new_values: dict[str, str | None] = {}
532
521
 
@@ -535,14 +524,14 @@ def resolve_variables(
535
524
  result = None
536
525
  else:
537
526
  atoms = parse_variables(value)
538
- env: dict[str, str | None] = {}
527
+ aenv: dict[str, str | None] = {}
539
528
  if override:
540
- env.update(os.environ)
541
- env.update(new_values)
529
+ aenv.update(env)
530
+ aenv.update(new_values)
542
531
  else:
543
- env.update(new_values)
544
- env.update(os.environ)
545
- result = ''.join(atom.resolve(env) for atom in atoms)
532
+ aenv.update(new_values)
533
+ aenv.update(env)
534
+ result = ''.join(atom.resolve(aenv) for atom in atoms)
546
535
 
547
536
  new_values[name] = result
548
537
 
@@ -556,6 +545,7 @@ def dotenv_values(
556
545
  verbose: bool = False,
557
546
  interpolate: bool = True,
558
547
  encoding: str | None = 'utf-8',
548
+ env: ta.Mapping[str, str] | None = None,
559
549
  ) -> dict[str, str | None]:
560
550
  """
561
551
  Parse a .env file and return its content as a dict.
@@ -583,4 +573,5 @@ def dotenv_values(
583
573
  interpolate=interpolate,
584
574
  override=True,
585
575
  encoding=encoding,
576
+ env=env,
586
577
  ).dict()
@@ -125,7 +125,7 @@ import functools
125
125
  import json as _json
126
126
  import typing as ta
127
127
 
128
- from . import lang
128
+ from .. import lang
129
129
 
130
130
 
131
131
  if ta.TYPE_CHECKING:
@@ -160,6 +160,7 @@ PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
160
160
  dump_pretty: ta.Callable[..., bytes] = functools.partial(dump, **PRETTY_KWARGS) # type: ignore
161
161
  dumps_pretty: ta.Callable[..., str] = functools.partial(dumps, **PRETTY_KWARGS)
162
162
 
163
+
163
164
  ##
164
165
 
165
166