omlish 0.0.0.dev3__py3-none-any.whl → 0.0.0.dev5__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 (91) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +8 -0
  3. omlish/asyncs/__init__.py +18 -0
  4. omlish/asyncs/anyio.py +66 -0
  5. omlish/asyncs/flavors.py +227 -0
  6. omlish/asyncs/trio_asyncio.py +47 -0
  7. omlish/c3.py +1 -1
  8. omlish/cached.py +1 -2
  9. omlish/collections/__init__.py +4 -1
  10. omlish/collections/cache/impl.py +1 -1
  11. omlish/collections/indexed.py +1 -1
  12. omlish/collections/utils.py +38 -6
  13. omlish/configs/__init__.py +5 -0
  14. omlish/configs/classes.py +53 -0
  15. omlish/configs/dotenv.py +586 -0
  16. omlish/configs/props.py +589 -49
  17. omlish/dataclasses/impl/api.py +1 -1
  18. omlish/dataclasses/impl/as_.py +1 -1
  19. omlish/dataclasses/impl/fields.py +1 -0
  20. omlish/dataclasses/impl/init.py +1 -1
  21. omlish/dataclasses/impl/main.py +1 -0
  22. omlish/dataclasses/impl/metaclass.py +6 -1
  23. omlish/dataclasses/impl/order.py +1 -1
  24. omlish/dataclasses/impl/reflect.py +15 -2
  25. omlish/defs.py +1 -1
  26. omlish/diag/procfs.py +29 -1
  27. omlish/diag/procstats.py +32 -0
  28. omlish/diag/replserver/console.py +3 -3
  29. omlish/diag/replserver/server.py +6 -5
  30. omlish/diag/threads.py +86 -0
  31. omlish/docker.py +19 -0
  32. omlish/dynamic.py +2 -2
  33. omlish/fnpairs.py +121 -24
  34. omlish/graphs/dags.py +113 -0
  35. omlish/graphs/domination.py +268 -0
  36. omlish/graphs/trees.py +2 -2
  37. omlish/http/__init__.py +25 -0
  38. omlish/http/asgi.py +131 -0
  39. omlish/http/consts.py +31 -4
  40. omlish/http/cookies.py +194 -0
  41. omlish/http/dates.py +70 -0
  42. omlish/http/encodings.py +6 -0
  43. omlish/http/json.py +273 -0
  44. omlish/http/sessions.py +197 -0
  45. omlish/inject/__init__.py +8 -2
  46. omlish/inject/bindings.py +3 -3
  47. omlish/inject/exceptions.py +3 -3
  48. omlish/inject/impl/elements.py +46 -25
  49. omlish/inject/impl/injector.py +8 -5
  50. omlish/inject/impl/multis.py +74 -0
  51. omlish/inject/impl/providers.py +19 -39
  52. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  53. omlish/inject/impl/scopes.py +4 -2
  54. omlish/inject/injector.py +1 -0
  55. omlish/inject/keys.py +3 -9
  56. omlish/inject/multis.py +70 -0
  57. omlish/inject/providers.py +23 -23
  58. omlish/inject/scopes.py +7 -3
  59. omlish/inject/types.py +0 -8
  60. omlish/iterators.py +13 -0
  61. omlish/json.py +138 -1
  62. omlish/lang/__init__.py +8 -0
  63. omlish/lang/classes/restrict.py +1 -1
  64. omlish/lang/classes/virtual.py +2 -2
  65. omlish/lang/contextmanagers.py +64 -0
  66. omlish/lang/datetimes.py +6 -5
  67. omlish/lang/functions.py +10 -0
  68. omlish/lang/imports.py +11 -2
  69. omlish/lang/sys.py +7 -0
  70. omlish/lang/typing.py +1 -0
  71. omlish/logs/utils.py +1 -1
  72. omlish/marshal/datetimes.py +1 -1
  73. omlish/reflect.py +8 -2
  74. omlish/sql/__init__.py +9 -0
  75. omlish/sql/asyncs.py +148 -0
  76. omlish/sync.py +70 -0
  77. omlish/term.py +6 -1
  78. omlish/testing/pydevd.py +2 -0
  79. omlish/testing/pytest/__init__.py +5 -0
  80. omlish/testing/pytest/helpers.py +0 -24
  81. omlish/testing/pytest/inject/harness.py +1 -1
  82. omlish/testing/pytest/marks.py +48 -0
  83. omlish/testing/pytest/plugins/__init__.py +2 -0
  84. omlish/testing/pytest/plugins/managermarks.py +60 -0
  85. omlish/testing/testing.py +10 -0
  86. omlish/text/delimit.py +4 -0
  87. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +4 -1
  88. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +91 -70
  89. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
  90. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
  91. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,70 @@
1
+ """
2
+ TODO:
3
+ - scopes
4
+ """
5
+ import collections.abc
6
+ import typing as ta
7
+
8
+ from .. import dataclasses as dc
9
+ from .. import lang
10
+ from .. import reflect as rfl
11
+ from .bindings import Binding
12
+ from .elements import Element
13
+ from .keys import Key
14
+ from .keys import as_key
15
+ from .providers import Provider
16
+
17
+
18
+ ##
19
+
20
+
21
+ def _check_set_multi_key(mk: Key) -> bool:
22
+ return rfl.get_concrete_type(mk.ty) is collections.abc.Set
23
+
24
+
25
+ @dc.dataclass(frozen=True)
26
+ @dc.extra_params(cache_hash=True)
27
+ class SetBinding(Element, lang.Final):
28
+ multi_key: Key = dc.xfield(check=_check_set_multi_key)
29
+ dst: Key = dc.xfield()
30
+
31
+
32
+ @dc.dataclass(frozen=True, eq=False)
33
+ class SetProvider(Provider):
34
+ multi_key: Key = dc.xfield(check=_check_set_multi_key)
35
+
36
+ def provided_ty(self) -> rfl.Type | None:
37
+ return self.multi_key.ty
38
+
39
+
40
+ def bind_set_provider(multi_key: ta.Any) -> Element:
41
+ multi_key = as_key(multi_key)
42
+ return Binding(multi_key, SetProvider(multi_key))
43
+
44
+
45
+ ##
46
+
47
+
48
+ def _check_map_multi_key(mk: Key) -> bool:
49
+ return rfl.get_concrete_type(mk.ty) is collections.abc.Mapping
50
+
51
+
52
+ @dc.dataclass(frozen=True)
53
+ @dc.extra_params(cache_hash=True)
54
+ class MapBinding(Element, lang.Final):
55
+ multi_key: Key = dc.xfield(check=_check_map_multi_key)
56
+ map_key: ta.Any = dc.xfield(())
57
+ dst: Key = dc.xfield(())
58
+
59
+
60
+ @dc.dataclass(frozen=True, eq=False)
61
+ class MapProvider(Provider):
62
+ multi_key: Key = dc.xfield(check=_check_map_multi_key)
63
+
64
+ def provided_ty(self) -> rfl.Type | None:
65
+ return self.multi_key.ty
66
+
67
+
68
+ def bind_map_provider(multi_key: ta.Any) -> Element:
69
+ multi_key = as_key(multi_key)
70
+ return Binding(multi_key, MapProvider(multi_key))
@@ -4,12 +4,12 @@ import typing as ta
4
4
  from .. import check
5
5
  from .. import dataclasses as dc
6
6
  from .. import lang
7
+ from .. import reflect as rfl
7
8
  from .elements import Element
8
9
  from .elements import Elements
9
10
  from .impl.inspect import signature
10
11
  from .keys import Key
11
12
  from .keys import as_key
12
- from .types import Cls
13
13
 
14
14
 
15
15
  class _Missing(lang.NotInstantiable):
@@ -21,7 +21,7 @@ class _Missing(lang.NotInstantiable):
21
21
 
22
22
  class Provider(lang.Abstract):
23
23
  @abc.abstractmethod
24
- def provided_cls(self) -> Cls | None:
24
+ def provided_ty(self) -> rfl.Type | None:
25
25
  raise NotImplementedError
26
26
 
27
27
 
@@ -47,19 +47,19 @@ def as_provider(o: ta.Any) -> Provider:
47
47
  @dc.dataclass(frozen=True, eq=False)
48
48
  class FnProvider(Provider):
49
49
  fn: ta.Any
50
- cls: Cls | None = None
50
+ ty: rfl.Type | None = None
51
51
 
52
- def provided_cls(self) -> Cls | None:
53
- return self.cls
52
+ def provided_ty(self) -> rfl.Type | None:
53
+ return self.ty
54
54
 
55
55
 
56
- def fn(fn: ta.Any, cls: Cls | None = _Missing) -> Provider:
56
+ def fn(fn: ta.Any, ty: rfl.Type | None = _Missing) -> Provider:
57
57
  check.not_isinstance(fn, type)
58
58
  check.callable(fn)
59
- if cls is _Missing:
59
+ if ty is _Missing:
60
60
  sig = signature(fn)
61
- cls = check.isinstance(sig.return_annotation, type)
62
- return FnProvider(fn, cls)
61
+ ty = check.isinstance(sig.return_annotation, type)
62
+ return FnProvider(fn, ty)
63
63
 
64
64
 
65
65
  ##
@@ -67,15 +67,15 @@ def fn(fn: ta.Any, cls: Cls | None = _Missing) -> Provider:
67
67
 
68
68
  @dc.dataclass(frozen=True, eq=False)
69
69
  class CtorProvider(Provider):
70
- cls: type
70
+ ty: type
71
71
 
72
- def provided_cls(self) -> Cls:
73
- return self.cls
72
+ def provided_ty(self) -> type:
73
+ return self.ty
74
74
 
75
75
 
76
- def ctor(cls: type) -> Provider:
77
- check.isinstance(cls, type)
78
- return CtorProvider(cls)
76
+ def ctor(ty: type) -> Provider:
77
+ check.isinstance(ty, type)
78
+ return CtorProvider(ty)
79
79
 
80
80
 
81
81
  ##
@@ -84,16 +84,16 @@ def ctor(cls: type) -> Provider:
84
84
  @dc.dataclass(frozen=True, eq=False)
85
85
  class ConstProvider(Provider):
86
86
  v: ta.Any
87
- cls: Cls | None = None
87
+ ty: rfl.Type | None = None
88
88
 
89
- def provided_cls(self) -> Cls | None:
90
- return self.cls
89
+ def provided_ty(self) -> rfl.Type | None:
90
+ return self.ty
91
91
 
92
92
 
93
- def const(v: ta.Any, cls: Cls | None = _Missing) -> Provider:
94
- if cls is _Missing:
95
- cls = type(v)
96
- return ConstProvider(v, cls)
93
+ def const(v: ta.Any, ty: rfl.Type | None = _Missing) -> Provider:
94
+ if ty is _Missing:
95
+ ty = type(v)
96
+ return ConstProvider(v, ty)
97
97
 
98
98
 
99
99
  ##
@@ -103,7 +103,7 @@ def const(v: ta.Any, cls: Cls | None = _Missing) -> Provider:
103
103
  class LinkProvider(Provider):
104
104
  k: Key
105
105
 
106
- def provided_cls(self) -> Cls | None:
106
+ def provided_ty(self) -> rfl.Type | None:
107
107
  return None
108
108
 
109
109
 
omlish/inject/scopes.py CHANGED
@@ -5,21 +5,25 @@ import typing as ta
5
5
  from .. import check
6
6
  from .. import dataclasses as dc
7
7
  from .. import lang
8
+ from .. import reflect as rfl
8
9
  from .bindings import Binding
9
10
  from .bindings import as_binding
10
11
  from .elements import Element
11
12
  from .keys import Key
12
13
  from .keys import as_key
13
14
  from .providers import Provider
14
- from .types import Cls
15
15
  from .types import Scope
16
16
 
17
+
17
18
  if ta.TYPE_CHECKING:
18
19
  from . import injector as injector_
19
20
  else:
20
21
  injector_ = lang.proxy_import('.injector', __package__)
21
22
 
22
23
 
24
+ ##
25
+
26
+
23
27
  @dc.dataclass(frozen=True)
24
28
  @dc.extra_params(cache_hash=True)
25
29
  class ScopeBinding(Element, lang.Final):
@@ -65,8 +69,8 @@ class ScopeSeededProvider(Provider):
65
69
  ss: SeededScope = dc.xfield(coerce=check.of_isinstance(SeededScope))
66
70
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
67
71
 
68
- def provided_cls(self) -> Cls | None:
69
- return self.key.cls
72
+ def provided_ty(self) -> rfl.Type | None:
73
+ return self.key.ty
70
74
 
71
75
 
72
76
  def bind_scope_seed(ss: SeededScope, k: ta.Any) -> Element:
omlish/inject/types.py CHANGED
@@ -1,17 +1,9 @@
1
- import typing as ta
2
-
3
1
  from .. import lang
4
2
 
5
3
 
6
4
  ##
7
5
 
8
6
 
9
- Cls = type | ta.NewType
10
-
11
-
12
- ##
13
-
14
-
15
7
  class Scope(lang.Abstract):
16
8
  def __repr__(self) -> str:
17
9
  return type(self).__name__
omlish/iterators.py CHANGED
@@ -218,3 +218,16 @@ def expand_indexed_pairs(
218
218
  if idx < width_:
219
219
  result[idx] = value
220
220
  return result
221
+
222
+
223
+ ##
224
+ # https://docs.python.org/3/library/itertools.html#itertools-recipes
225
+
226
+
227
+ def sliding_window(it: ta.Iterable[T], n: int) -> ta.Iterator[tuple[T, ...]]:
228
+ # sliding_window('ABCDEFG', 4) -> ABCD BCDE CDEF DEFG
229
+ iterator = iter(it)
230
+ window = collections.deque(itertools.islice(iterator, n - 1), maxlen=n)
231
+ for x in iterator:
232
+ window.append(x)
233
+ yield tuple(window)
omlish/json.py CHANGED
@@ -1,7 +1,143 @@
1
+ """
2
+ json
3
+ dump
4
+ skipkeys=False
5
+ ensure_ascii=True
6
+ check_circular=True
7
+ allow_nan=True
8
+ cls=None
9
+ indent=None
10
+ separators=None
11
+ default=None
12
+ sort_keys=False
13
+ dumps
14
+ ^
15
+ load
16
+ cls=None
17
+ object_hook=None
18
+ parse_float=None
19
+ parse_int=None
20
+ parse_constant=None
21
+ object_pairs_hook=None
22
+ loads
23
+ ^
24
+
25
+ ujson
26
+ dump
27
+ ensure_ascii
28
+ encode_html_chars
29
+ escape_forward_slashes
30
+ sort_keys
31
+ indent
32
+ allow_nan
33
+ reject_bytes
34
+ default
35
+ separators
36
+ dumps
37
+ ^
38
+ load
39
+ loads
40
+
41
+ orjson
42
+ dumps
43
+ default
44
+ option
45
+ OPT_INDENT_2
46
+ OPT_NAIVE_UTC
47
+ OPT_NON_STR_KEYS
48
+ OPT_OMIT_MICROSECONDS
49
+ OPT_PASSTHROUGH_DATACLASS
50
+ OPT_PASSTHROUGH_DATETIME
51
+ OPT_PASSTHROUGH_SUBCLASS
52
+ OPT_SERIALIZE_DATACLASS
53
+ OPT_SERIALIZE_NUMPY
54
+ OPT_SERIALIZE_UUID
55
+ OPT_SORT_KEYS
56
+ OPT_STRICT_INTEGER
57
+ OPT_UTC_Z
58
+ loads
59
+
60
+ rapidjson
61
+ dump
62
+ skipkeys=False,
63
+ ensure_ascii=True,
64
+ write_mode=WM_COMPACT,
65
+ WM_COMPACT
66
+ WM_PRETTY
67
+ WM_SINGLE_LINE_ARRAY
68
+ indent=4,
69
+ default=None,
70
+ sort_keys=False,
71
+ number_mode=None,
72
+ NM_NONE
73
+ NM_DECIMAL
74
+ NM_NAN
75
+ NM_NATIVE
76
+ datetime_mode=None,
77
+ DM_NONE
78
+ DM_ISO8601
79
+ DM_UNIX_TIME
80
+ DM_ONLY_SECONDS
81
+ DM_IGNORE_TZ
82
+ DM_NAIVE_IS_UTC
83
+ DM_SHIFT_TO_UTC
84
+ uuid_mode=None,
85
+ UM_NONE
86
+ UM_CANONICAL
87
+ UM_HEX
88
+ bytes_mode=BM_UTF8,
89
+ BM_NONE
90
+ BM_UTF8
91
+ iterable_mode=IM_ANY_ITERABLE,
92
+ IM_ANY_ITERABLE
93
+ IM_ONLY_LISTS
94
+ mapping_mode=MM_ANY_MAPPING,
95
+ MM_ANY_MAPPING
96
+ MM_ONLY_DICTS
97
+ MM_COERCE_KEYS_TO_STRINGS
98
+ MM_SKIP_NON_STRING_KEYS
99
+ MM_SORT_KEYS
100
+ chunk_size
101
+ allow_nan=True
102
+ dumps
103
+ ^
104
+ -chunk_size
105
+ load
106
+ object_hook=None,
107
+ number_mode=None,
108
+ ^
109
+ datetime_mode=None,
110
+ ^
111
+ uuid_mode=None,
112
+ ^
113
+ parse_mode=None,
114
+ PM_NONE
115
+ PM_COMMENTS
116
+ PM_TRAILING_COMMAS
117
+ chunk_size=65536,
118
+ allow_nan=True
119
+ loads
120
+ ^
121
+ -chunk_size
122
+
123
+ """
1
124
  import functools
2
125
  import json as _json
3
126
  import typing as ta
4
127
 
128
+ from . import lang
129
+
130
+
131
+ if ta.TYPE_CHECKING:
132
+ import orjson as _orjson
133
+ import ujson as _ujson
134
+ else:
135
+ _orjson = lang.proxy_import('orjson')
136
+ _ujson = lang.proxy_import('ujson')
137
+
138
+
139
+ ##
140
+
5
141
 
6
142
  dump = _json.dump
7
143
  dumps = _json.dumps
@@ -11,6 +147,7 @@ detect_encoding = _json.detect_encoding
11
147
  load = _json.load
12
148
  loads = _json.loads
13
149
 
150
+
14
151
  ##
15
152
 
16
153
 
@@ -29,7 +166,7 @@ dumps_pretty: ta.Callable[..., str] = functools.partial(dumps, **PRETTY_KWARGS)
29
166
  COMPACT_SEPARATORS = (',', ':')
30
167
 
31
168
  COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
32
- indent=0,
169
+ indent=None,
33
170
  separators=COMPACT_SEPARATORS,
34
171
  )
35
172
 
omlish/lang/__init__.py CHANGED
@@ -46,7 +46,9 @@ from .cmp import ( # noqa
46
46
  )
47
47
 
48
48
  from .contextmanagers import ( # noqa
49
+ AsyncContextManager,
49
50
  ContextManaged,
51
+ ContextManager,
50
52
  ContextWrapped,
51
53
  DefaultLockable,
52
54
  ExitStacked,
@@ -90,6 +92,7 @@ from .exceptions import ( # noqa
90
92
  from .functions import ( # noqa
91
93
  Args,
92
94
  VoidError,
95
+ as_async,
93
96
  constant,
94
97
  finally_,
95
98
  identity,
@@ -108,6 +111,7 @@ from .functions import ( # noqa
108
111
  )
109
112
 
110
113
  from .imports import ( # noqa
114
+ can_import,
111
115
  import_all,
112
116
  import_module,
113
117
  import_module_attr,
@@ -156,6 +160,10 @@ from .strings import ( # noqa
156
160
  snake_case,
157
161
  )
158
162
 
163
+ from .sys import ( # noqa
164
+ is_gil_enabled,
165
+ )
166
+
159
167
  from .timeouts import ( # noqa
160
168
  DeadlineTimeout,
161
169
  InfiniteTimeout,
@@ -89,7 +89,7 @@ class PackageSealed:
89
89
  class NotInstantiable(Abstract):
90
90
  __slots__ = ()
91
91
 
92
- def __new__(cls, *args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn: # noqa
92
+ def __new__(cls, *args, **kwargs): # noqa
93
93
  raise TypeError
94
94
 
95
95
 
@@ -3,8 +3,8 @@ import types
3
3
  import typing as ta
4
4
 
5
5
  from .abstract import make_abstract
6
- from .restrict import NotInstantiable
7
6
  from .restrict import Final
7
+ from .restrict import NotInstantiable
8
8
 
9
9
 
10
10
  T = ta.TypeVar('T')
@@ -122,7 +122,7 @@ class Callable(NotInstantiable, Final, ta.Generic[T]):
122
122
 
123
123
  @classmethod
124
124
  def __subclasscheck__(cls, subclass: type) -> bool:
125
- if not hasattr(subclass, '__call__'):
125
+ if not hasattr(subclass, '__call__'): # noqa
126
126
  return False
127
127
  call = subclass.__call__
128
128
  if isinstance(call, types.MethodWrapperType) and call.__self__ is subclass:
@@ -1,3 +1,4 @@
1
+ import abc
1
2
  import contextlib
2
3
  import contextvars
3
4
  import functools
@@ -50,6 +51,69 @@ NOP_CONTEXT_MANAGER = NopContextManager()
50
51
  ##
51
52
 
52
53
 
54
+ class ContextManager(abc.ABC, ta.Generic[T]):
55
+
56
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
57
+ super().__init_subclass__(**kwargs)
58
+
59
+ if not hasattr(cls.__contextmanager__, '_is_contextmanager'):
60
+ cls.__contextmanager__ = contextlib.contextmanager(cls.__contextmanager__) # type: ignore # noqa
61
+ cls.__contextmanager__._is_contextmanager = True # type: ignore # noqa
62
+
63
+ @abc.abstractmethod
64
+ def __contextmanager__(self) -> ta.Iterable[T]:
65
+ raise NotImplementedError
66
+
67
+ __contextmanager__._is_contextmanager = True # type: ignore # noqa
68
+
69
+ _contextmanager: ta.Any
70
+
71
+ def __enter__(self) -> T:
72
+ self._contextmanager = self.__contextmanager__()
73
+ return self._contextmanager.__enter__() # type: ignore
74
+
75
+ def __exit__(
76
+ self,
77
+ exc_type: type[BaseException] | None,
78
+ exc_val: BaseException | None,
79
+ exc_tb: types.TracebackType | None,
80
+ ) -> None:
81
+ return self._contextmanager.__exit__(exc_type, exc_val, exc_tb)
82
+
83
+
84
+ class AsyncContextManager(abc.ABC, ta.Generic[T]):
85
+
86
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
87
+ super().__init_subclass__(**kwargs)
88
+
89
+ if not hasattr(cls.__asynccontextmanager__, '_is_asynccontextmanager'):
90
+ cls.__asynccontextmanager__ = contextlib.asynccontextmanager(cls.__asynccontextmanager__) # type: ignore # noqa
91
+ cls.__asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
92
+
93
+ @abc.abstractmethod
94
+ def __asynccontextmanager__(self) -> ta.AsyncIterator[T]:
95
+ raise NotImplementedError
96
+
97
+ __asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
98
+
99
+ _asynccontextmanager: ta.Any
100
+
101
+ async def __aenter__(self) -> T:
102
+ self._asynccontextmanager = self.__asynccontextmanager__()
103
+ return await self._asynccontextmanager.__aenter__() # type: ignore
104
+
105
+ async def __aexit__(
106
+ self,
107
+ exc_type: type[BaseException] | None,
108
+ exc_val: BaseException | None,
109
+ exc_tb: types.TracebackType | None,
110
+ ) -> None:
111
+ return await self._asynccontextmanager.__aexit__(exc_type, exc_val, exc_tb)
112
+
113
+
114
+ ##
115
+
116
+
53
117
  @contextlib.contextmanager
54
118
  def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
55
119
  try:
omlish/lang/datetimes.py CHANGED
@@ -18,17 +18,18 @@ def months_ago(date: datetime.date, num: int) -> datetime.date:
18
18
  return datetime.date(ago_year, ago_month, 1)
19
19
 
20
20
 
21
- def parse_date(s: str) -> datetime.date:
21
+ def parse_date(s: str, tz: datetime.timezone | None = None) -> datetime.date:
22
+
22
23
  if s.lower() in ['today', 'now']:
23
- return datetime.date.today()
24
+ return datetime.datetime.now(tz=tz)
24
25
  elif s.lower() == 'yesterday':
25
- return datetime.date.today() - datetime.timedelta(days=1)
26
+ return datetime.datetime.now(tz=tz) - datetime.timedelta(days=1)
26
27
  elif s.lower().endswith(' days ago'):
27
28
  num = int(s.split(' ', 1)[0])
28
- return datetime.date.today() - datetime.timedelta(days=num)
29
+ return datetime.datetime.now(tz=tz) - datetime.timedelta(days=num)
29
30
  elif s.lower().endswith(' months ago'):
30
31
  months = int(s.split(' ', 1)[0])
31
- return months_ago(datetime.date.today(), months)
32
+ return months_ago(datetime.datetime.now(tz=tz), months)
32
33
  else:
33
34
  return datetime.date(*map(int, s.split('-', 3)))
34
35
 
omlish/lang/functions.py CHANGED
@@ -148,6 +148,16 @@ def void(*args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
148
148
  ##
149
149
 
150
150
 
151
+ def as_async(fn: ta.Callable[P, T]) -> ta.Callable[P, ta.Awaitable[T]]:
152
+ @functools.wraps(fn)
153
+ async def inner(*args, **kwargs):
154
+ return fn(*args, **kwargs)
155
+ return inner
156
+
157
+
158
+ ##
159
+
160
+
151
161
  _MISSING = object()
152
162
 
153
163
 
omlish/lang/imports.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import contextlib
2
2
  import functools
3
- import importlib.resources
3
+ import importlib.util
4
4
  import sys
5
5
  import types
6
6
  import typing as ta
@@ -11,6 +11,13 @@ from .cached import cached_function
11
11
  ##
12
12
 
13
13
 
14
+ def can_import(name: str, package: str | None = None) -> bool:
15
+ return importlib.util.find_spec(name, package) is not None
16
+
17
+
18
+ ##
19
+
20
+
14
21
  def lazy_import(name: str, package: str | None = None) -> ta.Callable[[], ta.Any]:
15
22
  return cached_function(functools.partial(importlib.import_module, name, package=package))
16
23
 
@@ -66,6 +73,8 @@ def yield_importable(
66
73
  filter: ta.Callable[[str], bool] | None = None, # noqa
67
74
  include_special: bool = False,
68
75
  ) -> ta.Iterator[str]:
76
+ from importlib import resources
77
+
69
78
  def rec(cur):
70
79
  if cur.split('.')[-1] == '__pycache__':
71
80
  return
@@ -83,7 +92,7 @@ def yield_importable(
83
92
  if getattr(module, '__file__', None) is None:
84
93
  return
85
94
 
86
- for file in importlib.resources.files(cur).iterdir():
95
+ for file in resources.files(cur).iterdir():
87
96
  if file.is_file() and file.name.endswith('.py'):
88
97
  if not (include_special or file.name not in SPECIAL_IMPORTABLE):
89
98
  continue
omlish/lang/sys.py ADDED
@@ -0,0 +1,7 @@
1
+ import sys
2
+
3
+
4
+ def is_gil_enabled() -> bool:
5
+ if (fn := getattr(sys, '_is_gil_enabled', None)) is not None:
6
+ return fn()
7
+ return True
omlish/lang/typing.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - typed_lambda is really kinda just 'annotate'
4
4
  - TypedLambda / TypedPartial classes, picklable, reprs
5
5
  - probably need to gen types per inst
6
+ - typed_factory
6
7
  """
7
8
  import functools
8
9
  import inspect
omlish/logs/utils.py CHANGED
@@ -12,7 +12,7 @@ def error_logging(log=log): # noqa
12
12
  try:
13
13
  return fn(*args, **kwargs)
14
14
  except Exception:
15
- log.exception(f'Error in {fn!r}')
15
+ log.exception('Error in %r', fn)
16
16
  raise
17
17
 
18
18
  return inner
@@ -67,7 +67,7 @@ class DatetimeUnmarshaler(Unmarshaler):
67
67
 
68
68
  for fmt in self.fmts:
69
69
  try:
70
- return datetime.datetime.strptime(v, fmt)
70
+ return datetime.datetime.strptime(v, fmt) # FIXME: timezone # noqa
71
71
  except ValueError:
72
72
  pass
73
73