omlish 0.0.0.dev484__py3-none-any.whl → 0.0.0.dev506__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 (93) hide show
  1. omlish/CODESTYLE.md +345 -0
  2. omlish/README.md +199 -0
  3. omlish/__about__.py +12 -5
  4. omlish/_check.cc +209 -0
  5. omlish/check.py +11 -0
  6. omlish/dataclasses/__init__.py +4 -0
  7. omlish/dataclasses/impl/concerns/frozen.py +4 -1
  8. omlish/dataclasses/impl/generation/plans.py +2 -17
  9. omlish/dataclasses/impl/generation/processor.py +2 -2
  10. omlish/dataclasses/impl/processing/driving.py +13 -1
  11. omlish/dataclasses/tools/replace.py +27 -0
  12. omlish/diag/_pycharm/runhack.py +1 -1
  13. omlish/dispatch/functions.py +1 -1
  14. omlish/formats/json/stream/lexing.py +13 -5
  15. omlish/formats/json/stream/parsing.py +1 -1
  16. omlish/inject/README.md +430 -0
  17. omlish/inject/__init__.py +20 -11
  18. omlish/inject/_dataclasses.py +1545 -1383
  19. omlish/inject/binder.py +7 -4
  20. omlish/inject/eagers.py +2 -4
  21. omlish/inject/elements.py +4 -0
  22. omlish/inject/helpers/late.py +76 -0
  23. omlish/inject/{managed.py → helpers/managed.py} +37 -34
  24. omlish/inject/impl/elements.py +7 -4
  25. omlish/inject/impl/injector.py +14 -26
  26. omlish/inject/impl/inspect.py +0 -8
  27. omlish/inject/impl/origins.py +1 -0
  28. omlish/inject/impl/privates.py +2 -6
  29. omlish/inject/impl/providers.py +0 -4
  30. omlish/inject/impl/scopes.py +14 -18
  31. omlish/inject/inspect.py +10 -1
  32. omlish/inject/multis.py +0 -3
  33. omlish/inject/scopes.py +7 -5
  34. omlish/io/buffers.py +35 -8
  35. omlish/lang/__init__.py +10 -0
  36. omlish/lang/classes/simple.py +2 -1
  37. omlish/lang/iterables.py +6 -0
  38. omlish/lang/objects.py +13 -0
  39. omlish/lang/outcomes.py +1 -1
  40. omlish/lang/recursion.py +1 -1
  41. omlish/lang/sequences.py +33 -0
  42. omlish/lifecycles/README.md +30 -0
  43. omlish/lifecycles/__init__.py +87 -13
  44. omlish/lifecycles/_dataclasses.py +1388 -0
  45. omlish/lifecycles/base.py +178 -64
  46. omlish/lifecycles/contextmanagers.py +113 -4
  47. omlish/lifecycles/controller.py +150 -87
  48. omlish/lifecycles/injection.py +143 -0
  49. omlish/lifecycles/listeners.py +56 -0
  50. omlish/lifecycles/managed.py +142 -0
  51. omlish/lifecycles/manager.py +218 -93
  52. omlish/lifecycles/states.py +2 -0
  53. omlish/lifecycles/transitions.py +3 -0
  54. omlish/lifecycles/unwrap.py +57 -0
  55. omlish/lite/maybes.py +7 -0
  56. omlish/lite/typing.py +33 -0
  57. omlish/logs/_amalg.py +1 -1
  58. omlish/logs/all.py +36 -11
  59. omlish/logs/asyncs.py +73 -0
  60. omlish/logs/base.py +101 -12
  61. omlish/logs/bisync.py +99 -0
  62. omlish/logs/contexts.py +4 -1
  63. omlish/logs/lists.py +125 -0
  64. omlish/logs/modules.py +19 -1
  65. omlish/logs/std/loggers.py +6 -1
  66. omlish/logs/std/noisy.py +11 -9
  67. omlish/logs/{standard.py → std/standard.py} +3 -4
  68. omlish/logs/utils.py +16 -1
  69. omlish/marshal/_dataclasses.py +813 -813
  70. omlish/reflect/__init__.py +43 -26
  71. omlish/reflect/ops.py +10 -1
  72. omlish/specs/jmespath/_dataclasses.py +597 -597
  73. omlish/specs/jsonschema/keywords/_dataclasses.py +244 -244
  74. omlish/sql/__init__.py +24 -5
  75. omlish/sql/api/dbapi.py +1 -1
  76. omlish/sql/dbapi/__init__.py +15 -0
  77. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  78. omlish/sql/queries/__init__.py +3 -0
  79. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  80. omlish/text/docwrap/cli.py +5 -0
  81. omlish/typedvalues/_collection.cc +500 -0
  82. omlish/typedvalues/collection.py +159 -62
  83. omlish/typedvalues/generic.py +5 -4
  84. omlish/typedvalues/values.py +6 -0
  85. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/METADATA +14 -9
  86. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/RECORD +92 -77
  87. omlish/lifecycles/abstract.py +0 -86
  88. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  89. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  90. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/WHEEL +0 -0
  91. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/entry_points.txt +0 -0
  92. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/licenses/LICENSE +0 -0
  93. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/top_level.txt +0 -0
omlish/_check.cc ADDED
@@ -0,0 +1,209 @@
1
+ // @omlish-cext
2
+ #define PY_SSIZE_T_CLEAN
3
+ #include "Python.h"
4
+ #include "structmember.h"
5
+
6
+ #include <vector>
7
+
8
+ //
9
+
10
+ #define _MODULE_NAME "_check"
11
+ #define _PACKAGE_NAME "omlish"
12
+ #define _MODULE_FULL_NAME _PACKAGE_NAME "." _MODULE_NAME
13
+
14
+ typedef struct check_state {
15
+ PyObject *typing_any;
16
+ PyTypeObject *nonetype;
17
+ } check_state;
18
+
19
+ static inline check_state * get_check_state(PyObject *module)
20
+ {
21
+ void *state = PyModule_GetState(module);
22
+ assert(state != NULL);
23
+ return (check_state *)state;
24
+ }
25
+
26
+ //
27
+
28
+ PyDoc_STRVAR(unpack_isinstance_spec_doc, "unpack_isinstance_spec(spec)");
29
+
30
+ static PyObject * unpack_isinstance_spec(PyObject *module, PyObject *spec)
31
+ {
32
+ check_state *state = get_check_state(module);
33
+
34
+ // If spec is a type, return (spec,)
35
+ if (PyType_Check(spec)) {
36
+ return PyTuple_Pack(1, spec);
37
+ }
38
+
39
+ PyObject *tuple_spec = nullptr;
40
+
41
+ // If not a tuple, wrap it in a tuple
42
+ if (!PyTuple_Check(spec)) {
43
+ tuple_spec = PyTuple_Pack(1, spec);
44
+ if (tuple_spec == nullptr) {
45
+ return nullptr;
46
+ }
47
+ } else {
48
+ // It's already a tuple, so we'll work with it
49
+ tuple_spec = spec;
50
+ Py_INCREF(tuple_spec);
51
+ }
52
+
53
+ // Check if None is in spec
54
+ Py_ssize_t size = PyTuple_Size(tuple_spec);
55
+ if (size < 0) {
56
+ Py_DECREF(tuple_spec);
57
+ return nullptr;
58
+ }
59
+
60
+ bool has_none = false;
61
+ bool has_any = false;
62
+
63
+ for (Py_ssize_t i = 0; i < size; i++) {
64
+ PyObject *item = PyTuple_GetItem(tuple_spec, i); // borrowed reference
65
+ if (item == nullptr) {
66
+ Py_DECREF(tuple_spec);
67
+ return nullptr;
68
+ }
69
+
70
+ if (item == Py_None) {
71
+ has_none = true;
72
+ }
73
+
74
+ // Check if item is typing.Any
75
+ if (state->typing_any != nullptr) {
76
+ int cmp = PyObject_RichCompareBool(item, state->typing_any, Py_EQ);
77
+ if (cmp < 0) {
78
+ Py_DECREF(tuple_spec);
79
+ return nullptr;
80
+ }
81
+ if (cmp) {
82
+ has_any = true;
83
+ }
84
+ }
85
+ }
86
+
87
+ // If typing.Any is in spec, return (object,)
88
+ if (has_any) {
89
+ Py_DECREF(tuple_spec);
90
+ return PyTuple_Pack(1, &PyBaseObject_Type);
91
+ }
92
+
93
+ // If None is in spec, filter it out and add NoneType
94
+ if (has_none) {
95
+ std::vector<PyObject*> filtered;
96
+ filtered.reserve(size);
97
+
98
+ for (Py_ssize_t i = 0; i < size; i++) {
99
+ PyObject *item = PyTuple_GetItem(tuple_spec, i); // borrowed reference
100
+ if (item != Py_None) {
101
+ filtered.push_back(item);
102
+ }
103
+ }
104
+
105
+ // Add NoneType
106
+ filtered.push_back((PyObject *)state->nonetype);
107
+
108
+ // Create new tuple
109
+ PyObject *result = PyTuple_New(filtered.size());
110
+ if (result == nullptr) {
111
+ Py_DECREF(tuple_spec);
112
+ return nullptr;
113
+ }
114
+
115
+ for (size_t i = 0; i < filtered.size(); i++) {
116
+ Py_INCREF(filtered[i]);
117
+ PyTuple_SET_ITEM(result, i, filtered[i]);
118
+ }
119
+
120
+ Py_DECREF(tuple_spec);
121
+ return result;
122
+ }
123
+
124
+ // Return the tuple as-is
125
+ return tuple_spec;
126
+ }
127
+
128
+ //
129
+
130
+ PyDoc_STRVAR(check_doc, "Native C++ implementations for omlish.lite.check");
131
+
132
+ static int check_exec(PyObject *module)
133
+ {
134
+ check_state *state = get_check_state(module);
135
+
136
+ // Get typing.Any
137
+ PyObject *typing_module = PyImport_ImportModule("typing");
138
+ if (typing_module == nullptr) {
139
+ // If typing module is not available, just set to nullptr
140
+ PyErr_Clear();
141
+ state->typing_any = nullptr;
142
+ } else {
143
+ state->typing_any = PyObject_GetAttrString(typing_module, "Any");
144
+ Py_DECREF(typing_module);
145
+ if (state->typing_any == nullptr) {
146
+ PyErr_Clear();
147
+ }
148
+ }
149
+
150
+ // Get NoneType (type(None))
151
+ state->nonetype = Py_TYPE(Py_None);
152
+ Py_INCREF(state->nonetype);
153
+
154
+ return 0;
155
+ }
156
+
157
+ static int check_traverse(PyObject *module, visitproc visit, void *arg)
158
+ {
159
+ check_state *state = get_check_state(module);
160
+ Py_VISIT(state->typing_any);
161
+ Py_VISIT(state->nonetype);
162
+ return 0;
163
+ }
164
+
165
+ static int check_clear(PyObject *module)
166
+ {
167
+ check_state *state = get_check_state(module);
168
+ Py_CLEAR(state->typing_any);
169
+ Py_CLEAR(state->nonetype);
170
+ return 0;
171
+ }
172
+
173
+ static void check_free(void *module)
174
+ {
175
+ check_clear((PyObject *)module);
176
+ }
177
+
178
+ static PyMethodDef check_methods[] = {
179
+ {"unpack_isinstance_spec", (PyCFunction)unpack_isinstance_spec, METH_O, unpack_isinstance_spec_doc},
180
+ {NULL, NULL, 0, NULL}
181
+ };
182
+
183
+ static struct PyModuleDef_Slot check_slots[] = {
184
+ {Py_mod_exec, (void *) check_exec},
185
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
186
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
187
+ {0, NULL}
188
+ };
189
+
190
+ static struct PyModuleDef check_module = {
191
+ .m_base = PyModuleDef_HEAD_INIT,
192
+ .m_name = _MODULE_NAME,
193
+ .m_doc = check_doc,
194
+ .m_size = sizeof(check_state),
195
+ .m_methods = check_methods,
196
+ .m_slots = check_slots,
197
+ .m_traverse = check_traverse,
198
+ .m_clear = check_clear,
199
+ .m_free = check_free,
200
+ };
201
+
202
+ extern "C" {
203
+
204
+ PyMODINIT_FUNC PyInit__check(void)
205
+ {
206
+ return PyModuleDef_Init(&check_module);
207
+ }
208
+
209
+ }
omlish/check.py CHANGED
@@ -26,6 +26,17 @@ _callable = callable
26
26
  ##
27
27
 
28
28
 
29
+ try:
30
+ from . import _check # type: ignore
31
+ except ImportError:
32
+ pass
33
+ else:
34
+ setattr(Checks, '_unpack_isinstance_spec', _check.unpack_isinstance_spec)
35
+
36
+
37
+ ##
38
+
39
+
29
40
  def register_on_raise(fn: OnRaiseFn) -> None:
30
41
  check.register_on_raise(fn)
31
42
 
@@ -138,6 +138,10 @@ with _lang.auto_proxy_init(globals()) as _api_cap:
138
138
 
139
139
  from .tools.replace import ( # noqa
140
140
  deep_replace,
141
+
142
+ replace_if,
143
+ replace_ne,
144
+ replace_is_not,
141
145
  )
142
146
 
143
147
  from .tools.repr import ( # noqa
@@ -79,6 +79,9 @@ class FrozenGenerator(Generator[FrozenPlan]):
79
79
  if not ctx.cs.frozen:
80
80
  return None
81
81
 
82
+ if issubclass(ctx.cls, BaseException):
83
+ raise TypeError('cannot use frozen=True with subclass of BaseException')
84
+
82
85
  return PlanResult(FrozenPlan(
83
86
  fields=tuple(f.name for f in ctx.cs.fields),
84
87
  allow_dynamic_dunder_attrs=ctx.cs.allow_dynamic_dunder_attrs,
@@ -118,7 +121,7 @@ class FrozenGenerator(Generator[FrozenPlan]):
118
121
  f'}}',
119
122
  f'',
120
123
  ])
121
- condition.append(f' or name in {set_ident}')
124
+ condition.append(f'or name in {set_ident}')
122
125
 
123
126
  return AddMethodOp(
124
127
  f'__{mth}__',
@@ -1,9 +1,4 @@
1
- """
2
- TODO:
3
- - sha1 is slow :/ key by repr but name by sha1
4
- """
5
1
  import dataclasses as dc
6
- import re
7
2
  import typing as ta
8
3
 
9
4
  from .... import lang
@@ -21,15 +16,5 @@ class Plans:
21
16
  return iter(self.tup)
22
17
 
23
18
  @lang.cached_function(no_wrapper_update=True)
24
- def render(self) -> str:
25
- return _render(self)
26
-
27
-
28
- ##
29
-
30
-
31
- _WS_PAT = re.compile(r'\s+')
32
-
33
-
34
- def _render(plans: Plans) -> str:
35
- return _WS_PAT.sub('', repr(plans.tup))
19
+ def repr(self) -> str:
20
+ return repr(self)
@@ -126,7 +126,7 @@ class GeneratorProcessor(Processor):
126
126
  ])
127
127
 
128
128
  if (vo := gp._ctx.option(Verbosity)) is not None and vo.debug: # noqa
129
- print(gp.prepare().plans.render(), file=sys.stderr)
129
+ print(gp.prepare().plans.repr(), file=sys.stderr)
130
130
  print(file=sys.stderr)
131
131
  print(comp_src, file=sys.stderr)
132
132
  print(file=sys.stderr)
@@ -235,7 +235,7 @@ class GeneratorProcessor(Processor):
235
235
  #
236
236
 
237
237
  prep = self.prepare()
238
- prep_plan_repr = repr(prep.plans)
238
+ prep_plan_repr = prep.plans.repr()
239
239
 
240
240
  #
241
241
 
@@ -1,5 +1,6 @@
1
1
  import contextlib
2
2
  import contextvars
3
+ import sys
3
4
  import typing as ta
4
5
 
5
6
  from .... import lang
@@ -36,6 +37,14 @@ def processing_options_context(*opts: ProcessingOption) -> ta.Iterator[None]:
36
37
  ##
37
38
 
38
39
 
40
+ def _is_pkg_init_mod(mod_name: str) -> bool:
41
+ if (mod_obj := sys.modules.get(mod_name)) is None:
42
+ return False
43
+ if (mod_spec := getattr(mod_obj, '__spec__', None)) is None:
44
+ return False
45
+ return bool(mod_spec.submodule_search_locations)
46
+
47
+
39
48
  def drive_cls_processing(
40
49
  cls: type,
41
50
  cs: ClassSpec,
@@ -53,7 +62,10 @@ def drive_cls_processing(
53
62
  #
54
63
 
55
64
  cls_mod = cls.__module__
56
- cls_pkg = cls_mod.rpartition('.')[0]
65
+ if _is_pkg_init_mod(cls_mod):
66
+ cls_pkg = cls_mod
67
+ else:
68
+ cls_pkg = cls_mod.rpartition('.')[0]
57
69
  pkg_cfg = lang.coalesce(PACKAGE_CONFIG_CACHE.get(cls_pkg), DEFAULT_NAMED_PACKAGE_CONFIG)
58
70
 
59
71
  #
@@ -1,4 +1,5 @@
1
1
  import dataclasses as dc
2
+ import operator
2
3
  import typing as ta
3
4
 
4
5
 
@@ -15,3 +16,29 @@ def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any
15
16
  return dc.replace(o, **args[0](o)) # type: ignore
16
17
  else:
17
18
  return dc.replace(o, **{args[0]: deep_replace(getattr(o, args[0]), *args[1:])}) # type: ignore
19
+
20
+
21
+ ##
22
+
23
+
24
+ def replace_if(
25
+ o: T,
26
+ fn: ta.Callable[[ta.Any, ta.Any], bool] = operator.eq,
27
+ /,
28
+ **kwargs: ta.Any,
29
+ ) -> T:
30
+ dct: dict[str, ta.Any] = {}
31
+ for k, v in kwargs.items():
32
+ if fn(getattr(o, k), v):
33
+ dct[k] = v # noqa
34
+ if not dct:
35
+ return o
36
+ return dc.replace(o, **dct) # type: ignore
37
+
38
+
39
+ def replace_ne(o: T, **kwargs: ta.Any) -> T:
40
+ return replace_if(o, operator.ne, **kwargs)
41
+
42
+
43
+ def replace_is_not(o: T, **kwargs: ta.Any) -> T:
44
+ return replace_if(o, operator.is_not, **kwargs)
@@ -95,7 +95,7 @@ _BOOL_ENV_VAR_VALUES = {
95
95
  def _get_opt_env_bool(n, d): # type: (str | None, bool) -> bool
96
96
  if n is None or n not in os.environ:
97
97
  return d
98
- return _BOOL_ENV_VAR_VALUES[os.environ[n]]
98
+ return _BOOL_ENV_VAR_VALUES[os.environ[n].lower()]
99
99
 
100
100
 
101
101
  def _get_env_path_list(k): # type: (str) -> list[str]
@@ -43,7 +43,7 @@ def function(func): # noqa
43
43
  wrapper.dispatch = disp.dispatch # type: ignore
44
44
 
45
45
  else:
46
- from x.c._dispatch import function_wrapper # noqa
46
+ from x.c._dispatch import function_wrapper # type: ignore
47
47
  wrapper = function_wrapper(
48
48
  disp.dispatch,
49
49
  **{k: getattr(func, k) for k in functools.WRAPPER_ASSIGNMENTS if hasattr(func, k)},
@@ -338,7 +338,6 @@ class JsonStreamLexer(GenMachine[str, Token]):
338
338
  self._line = line
339
339
  self._col = col
340
340
 
341
- last = None
342
341
  while True:
343
342
  c: str | None = None
344
343
 
@@ -358,7 +357,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
358
357
  ofs += skip_to - char_in_str_pos
359
358
  if (np := char_in_str.rfind('\n', char_in_str_pos, skip_to)) >= 0:
360
359
  line += char_in_str.count('\n', char_in_str_pos, skip_to)
361
- col = np - char_in_str_pos
360
+ col = skip_to - np - 1
362
361
  else:
363
362
  col += skip_to - char_in_str_pos
364
363
  buf.write(char_in_str[char_in_str_pos:skip_to])
@@ -406,9 +405,18 @@ class JsonStreamLexer(GenMachine[str, Token]):
406
405
  self._raise(f'Unterminated string literal: {buf.getvalue()}')
407
406
 
408
407
  buf.write(c)
409
- if c == q and last != '\\':
410
- break
411
- last = c
408
+ if c == q:
409
+ # Count consecutive backslashes before this quote
410
+ backslash_count = 0
411
+ buf_val = buf.getvalue()
412
+ check_pos = len(buf_val) - 2 # -2 because we just wrote the quote
413
+ while check_pos >= 0 and buf_val[check_pos] == '\\':
414
+ backslash_count += 1
415
+ check_pos -= 1
416
+
417
+ # Quote is escaped only if preceded by odd number of backslashes
418
+ if backslash_count % 2 == 0:
419
+ break
412
420
 
413
421
  restore_state()
414
422
 
@@ -215,7 +215,7 @@ class JsonStreamParser(GenMachine[Token, Event]):
215
215
  except GeneratorExit:
216
216
  raise JsonStreamParseError('Expected object body') from None
217
217
 
218
- if tok.kind == 'STRING' or (self._allow_trailing_commas and tok.kind == 'IDENT'):
218
+ if tok.kind == 'STRING' or (self._allow_extended_idents and tok.kind == 'IDENT'):
219
219
  k = tok.value
220
220
 
221
221
  try: