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/CODESTYLE.md ADDED
@@ -0,0 +1,345 @@
1
+ ### Environment
2
+
3
+ - Target cpython 3.13 - use the modern language and library features it includes.
4
+ - \[**lite**\] The exception is 'lite' code, which targets python 3.8.
5
+ - **A module is declared as being lite by having a `# @omlish-lite` comment at the top of it, or at the top of any
6
+ `__init__` module in its or any ancestor's package.**
7
+ - As a reminder, non-\[**lite**\] core is referred to as 'standard' code.
8
+ - Code should run on modern macOS and Linux - Windows support is not necessary, but still prefer things like
9
+ `os.path.join` over `'/'.join` where reasonable.
10
+
11
+
12
+ ### Dependencies
13
+
14
+ - Outside of a few specific subpackages (and test code), there are no external dependencies of any kind to rely on.
15
+ Use the standard library liberally, use `omlish` for everything else.
16
+
17
+ - All external runtime dependencies are optional, and generally fit into the following categories:
18
+ - Cryptography: `cryptography`. Its use is optional.
19
+ - File formats: `orjson`, `pyyaml`, `cbor2`, `lxml`, `cloudpickle`, etc. Wherever possible, they serve only as
20
+ accelerators and their absence will not block functionality - code should strive to have internal fallbacks that
21
+ prefer correctness to speed.
22
+ - Text formats: `jinja2`, `markdown-it-py`, etc. Particularly in LLM stuff the full versions of these are
23
+ unavoidable, but simplified internal 'equivalents' exist for simpler usecases.
24
+ - Compression algorithms: `lz4`, `zstandard`, `python-snappy`, `brotli`, etc. These generally have no internal
25
+ fallback if not present in the standard library, but are usually optional at runtime in practice.
26
+ - Database drivers: `pg8000`, `psycopg`, `psycopg2`, `mysql-connecteor-python`, `mysqlclient`, `pymysql`,
27
+ `snowflake-connector-python`, `duckdb`, etc. These also generally have no internal fallback.
28
+ - Large <span aria-label="math">m̶̡̢̡̢̠̥͎͇̯̥̹̪͇͇͇̺̟͋̓͂̇͝͝a̴̧̛̞̾̊͒̈́̿͗̓̐̊͝t̸̥͖͂̀̆͛́̅́͝͠ȟ̴̢͎͙͍̱̒͂́̆̽̽̈́͝</span> libraries: `numpy`, `torch`, `mlx`, `tinygrad`, `transformers`,
29
+ `llama-cpp-python`, `tokenizers`, etc. These tend to have gigantic interface surface areas compared to the
30
+ previous categories, and all interaction them is heavily quarantined to a single isolated package per dependency.
31
+ Outside of these isolated packages they absolutely cannot be assumed to be present.
32
+ - `textual`, specifically - it is the sole chosen TUI library. Almost all terminal functionality throughout the
33
+ codebase is usable without it - it only powers a small number of specific, larger TUI apps.
34
+ - Async backends: `trio`, `anyio`, `trio-asyncio`. Except in specific situations (such as under `textual`) async
35
+ code is not assumed to be running under asyncio, and in general async code should use anyio.
36
+ - **NOTE:** this is in flux.
37
+ - The 'hyper stack' for production web serving: `h11`, `h2`, `wsproto`. There are simpler internal http servers for
38
+ local and development use.
39
+ - Unique, focused, irreproducible, core utility libraries: `executing` / `asttokens`, `greenlet`, `wrapt`. In
40
+ general 'core' utility libraries are either avoided, replaced with an equivalent internal implementation, or in
41
+ small cases (often for \[**lite**\] code) vendored (preserving licenses, copyrights, and attribution) such as in
42
+ parts of `omdev.packaging`. However, there is a small number of libraries that do things I have absolutely no
43
+ interest in attempting or maintaining myself, such as those listed here. As with all other deps these are strictly
44
+ optional, and fallbacks exist wherever possible.
45
+ - `httpx` specifically. It is **NOT** required - various internal async http client options exist. It is however an
46
+ optional integration.
47
+ - Various other optional backends: `psutil`, `mwparserfromhell`, `regex`, `ddgs`, `tree-sitter`, etc.
48
+ - Notably absent from this list:
49
+ - `pydantic`. Use dataclasses.
50
+ - `click`. Use argparse.
51
+ - Any 'web client' library: `boto3`, `google-api-python-client`, `openai`, `anthropic`, etc. These are not used,
52
+ even optionally, in the codebase. All interaction with such api's is done with internal clients, usually via
53
+ dataclasses.
54
+ - Note: references to boto exist in code but only for code generation and cross-validation testing. Boto is not
55
+ used for production aws interaction.
56
+ - `gitpython`, `docker`, etc. Drive their cli's through a subprocess or talk to the api through the socket.
57
+ - `rich` (outside of `textual`). Use `omlish.term`, or simple inline escape codes, or just output plain text.
58
+ - `loguru` / `logbook` / `structlog`. Use `omlish.logs` or just stdlib `logging`.
59
+ - `json5`. Use `omlish.formats.json5`.
60
+ - Various specs: `jsonrpc`, `jsonschema`, `openapi`, `mcp`. Internal implementations exist.
61
+ - Web frameworks: `flask`, `fastapi`, `starlette`, etc. Equivalent internal patterns exist.
62
+
63
+
64
+ ### Structure
65
+
66
+ - In general, strongly prefer clusters of small source files to a single or small number of large ones - a few hundred
67
+ lines of code is a good target maximum, and there is no minimum.
68
+ - Our code is 'type-heavy', and small modules defining nothing but interrelated stateless structures are welcome.
69
+ - While not necessarily the case, organize packages as if it were to be managed by a guice-style dependency injector,
70
+ with package-level injector modules that each refer to their child packages' injector modules and so on.
71
+ - With the exception of root packages (`om*` directories), (relatively) deep package nesting is preferred over large
72
+ flat packages with many, less tightly related modules.
73
+ - Structurally quarantine all interaction with any external dependencies.
74
+ - For small, simple integrations, it may be done within a single module as an optional implementation of an
75
+ interface (or simple function conforming to a signature)
76
+ - For intermediate integrations, prefer a separate module dedicated to that integration.
77
+ - For large, complex integrations, prefer a more root-level package for that integration, whose internal structure
78
+ mirrors that of the core code it's being integrated with.
79
+ - In general the structure should mirror a 'Clean' or 'Hexagonal' architecture:
80
+ > Source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about
81
+ something in an outer circle.
82
+
83
+
84
+ ### Naming
85
+
86
+ - Module names should be nouns (usually plural or gerunds), not verbs, so as to not clash with function names. A
87
+ module should be named `parsing.py`, not `parse.py`, so `__init__.py` could `from .parsing import parse` without
88
+ shadowing the module itself.
89
+ - Function names should be verbs.
90
+ - When naming interface classes, the interface should be the 'bare' name, and implementations should have prefixes and
91
+ suffixes. For example, a user service interface would be `UserService`, with a `DbUserService` or `DictUserService`
92
+ subclass, or even a `UserServiceImpl` subclass if there is only one sensible initial implementation but it still
93
+ justifies being abstracted.
94
+ - When using acronyms, only the first letter of the acronym should be uppercased when it appears in CamelCased names
95
+ so as to distinguish it from adjacent acronyms. For example, a class to parse ABNF grammars would be `AbnfParser`,
96
+ and a class to parse the JSON ABNF grammar would be `JsonAbnfParser`.
97
+
98
+
99
+ ### Imports
100
+
101
+ - **Always** use relative imports within a package. **Never** reference the name of the root package from within
102
+ itself. For example, within the `omlish` package, it's `from . import lang`, not `from omlish import lang`. Within
103
+ the `omlish` root package there should never be an import line containing the word `omlish` - and references to the
104
+ root name should be avoided in general.
105
+ - Use the following import aliases for the following modules if they are used:
106
+ - `import dataclasses as dc`
107
+ - `import typing as ta`
108
+ - For *package-internal* imports, almost always import specific items rather than whole modules or packages.
109
+ - Here, 'package-internal' is loosely defined as the layer which 'external' users of the code will treat as the
110
+ 'public' interface.
111
+ - For other imports, strongly prefer to import a module or package, rather than importing specific items from it. So
112
+ for example, use `import typing as ta; fn: ta.Callable ...` as opposed to
113
+ `from typing import Callable; fn: Callable ...`, and `import dataclasses as dc; @dc.dataclass() ...` as opposed to
114
+ `from dataclasses import dataclass; @dataclass() ...`.
115
+ - Unless instructed or unavoidable, prefer to use only the standard library and the current existing codebase.
116
+ - Notable exceptions include:
117
+ - anyio - In general async code should write to anyio rather than asyncio (or trio) unless it is specifically
118
+ being written for a particular backend.
119
+ - **NOTE:** this is in flux.
120
+ - pytest - Write tests in pytest-style, and assume it is available.
121
+ - \[**lite**\] 'lite' code can have no external dependencies of any kind, and can only reference other 'lite' code.
122
+ - Lite async code uses only asyncio, and only uses functionality available in python 3.8.
123
+ - Lite tests are written with the unittest package.
124
+ - Unless forced to for external interoperability, avoid `pathlib` - use `os.path` instead.
125
+ - For heavy or optional imports, **ALWAYS** import whole modules, **not** individual module contents. For example, use
126
+ `import torch; t = torch.Tensor(...` rather than `from torch import Tensor; t = Tensor(...`.
127
+ - Rationale: we have a lazy import mechanism that operates at the module level. Do not attempt to manually
128
+ late-import such libraries, just import them as regular modules.
129
+
130
+
131
+ ### Modules
132
+
133
+ - Avoid global state in general. Constants are however fine.
134
+ - Do basically no 'work' in module body:
135
+ - *NEVER* eagerly do any IO in module body - wrap any such things in a `@lang.cached_function`.
136
+ - *NEVER* write dumb python 'scripts'. Modules intended as entrypoints are great, but *ALWAYS* make them importable
137
+ side-effect-free, and *ALWAYS* have an `if __name__ == '__main__'` guard at the bottom. And almost always have
138
+ that just call a `def _main() -> None:` or `async def _a_main() -> None:`. Don't pollute module globals with state
139
+ even if the module is running as entrypoint.
140
+ - Avoid temporary values in module global scope - for example, construction of a global effectively const `Mapping`
141
+ via a comprehension is fine in module body (as comprehension variables do not leak out to parent scope), but a bare
142
+ for loop in module body is not okay (as the loop variables and any intermediates in the loop body will be left as
143
+ globals). Instead, prefer to define and call a module private function which returns the desired global value.
144
+ - Always use relative imports even in python modules intended to be directly executed. All python invocations will
145
+ always be done via `python -m`.
146
+
147
+
148
+ ### Classes
149
+
150
+ - Ensure constructors call `super().__init__()`, even if they don't appear to inherit from anything at their
151
+ definition - *except* if the class is `@ta.Final` and there is explicit reason to not.
152
+ - A blank line should follow the super call if it is the first statement of the method (which it usually is) and
153
+ there are more statements in the method.
154
+ - Prefer to use dataclasses for even moderately complex usecases - if there are, say, more than a 2-element tuple, a
155
+ dataclass should probably be used.
156
+ - `ta.NamedTuple` still has limited usecases, such as replacing a function's return type from an anonymous tuple to a
157
+ named one (to allow it to be destructured by callers as before), or as a cache key, but in general almost always
158
+ prefer dataclasses.
159
+ - If a number of related functions are passing around a growing number of the common args/kwargs, don't be shy about
160
+ refactoring them into methods on a common class with shared immutable and mutable state - if the class is considered
161
+ private implementation detail and not part of any public api.
162
+ - For any necessary global state involving multiple interrelated variables, consider encapsulating it in a class, even
163
+ if it's only a private singleton.
164
+ - If appropriate, lean towards stateless classes, taking dependencies as constructor arguments and not mutating them
165
+ once set, and passing around and returning dataclasses for intermediate data. If however significant mutable shared
166
+ state is involved just use regular private class fields.
167
+ - Such dependencies should not be exposed publicly for other code to lazily piggyback off of: avoid transitive
168
+ dependencies.
169
+ - While not necessarily the case, write code as if it were to be managed by a constructor-injecting guice-style
170
+ dependency injector.
171
+ - Ideally, classes have their functional dependencies as ctor kwargs, and if possible these have sensible defaults.
172
+ - In such cases, prefer to have a kwarg default value for primitives and immutable values, otherwise prefer an
173
+ ` | None = None` kwarg and instantiate the default value in the ctor if necessary.
174
+ - Strongly prefer composition over inheritance.
175
+ - Prefer relatively fine-grained dependency decomposition.
176
+ - **STRONGLY** avoid giant, monolithic classes containing _eVeRyThInG_ anyone will ever need. Avoid classes like
177
+ `AppContext` and `AppConfig` each having large number of fields, *even if* those fields are arbitrarily deeply
178
+ nested within otherwise well-typed child objects.
179
+ - For situations in which different behaviors are necessary, prefer to define an interface or abstract class with
180
+ `@abc.abstractmethod` members, and write multiple implementations of them as warranted. Prefer to refer to the
181
+ interface in type annotations unless it must specifically refer to a given implementation.
182
+ - Protocols are to be used sparingly where they make sense. In general, nominal typing is a good thing - it is
183
+ desirable that not all functions that return an `int` are `UserIdProvider`'s.
184
+ - An abstract class with nothing but abstract (or constant) members is referred to as an interface. In general, prefer
185
+ pure interfaces as opposed to full abstract classes containing partial implementations at package public interface
186
+ boundaries.
187
+ - Do not use `abc.ABC` - in standard code use `lang.Abstract` and in lite code use `omlish.lite.abstract.Abstract`.
188
+ - Rationale: `abc.ABCMeta` adds extreme overhead to `isinstance` / `issubclass` checks (6x) in order to support
189
+ virtual base classes, which are almost never needed or desirable.
190
+ - Abstract methods should always do nothing but `raise NotImplementedError` - but they *must* do that.
191
+ - Properties should be free of side-effects.
192
+ - Rationale: Many utilities eagerly inspect properties at runtime, even private (underscore-prefixed) ones, so they
193
+ cannot alter state.
194
+ - Outside of rare, specific instances, **DO NOT** expose mutable internal class state.
195
+ - Keep all mutable state private as single-underscore-prefixed fields.
196
+ - As necessary for usage, expose internal state via methods or `@property`'s. For such cases do one of the
197
+ following:
198
+ - Type-annotate the return type as immutable. For example, a property exposing an internal `list[int]` would be
199
+ annotated as returning a `ta.Sequence[int]`, and a `dict[int, str]` would be annotated as returning a
200
+ `ta.Mapping[int, str]`.
201
+ - Return a defensive copy of the internal state. For example, a property returning an internal `list[int]` would
202
+ return a copy of the internal list.
203
+
204
+
205
+ ### Dataclasses
206
+
207
+ - Do not use bare, un-called `@dc.dataclass` as a decorator - always use `@dc.dataclass()` even if it is given no
208
+ arguments.
209
+ - **Strongly** prefer frozen dataclasses.
210
+ - In standard code, prefer to `from omlish import dataclasses as dc` - not the standard library `dataclasses` module.
211
+ The interface and behavior is the same.
212
+
213
+
214
+ ### Exceptions
215
+
216
+ - **Never** use the `assert` statement anywhere but test code - rather, check a condition and raise an `Exception` if
217
+ necessary.
218
+ - Prefer to use the 'check' system (`from omlish import check`, or `from omlish.lite.check import check` for lite
219
+ code) where `assert` would otherwise be used.
220
+ - Outside of `TypeError`, `ValueError`, and `RuntimeError`, prefer to create custom subclasses of `Exception` for more
221
+ specific errors. Use inheritance where beneficial to communicate subtypes of errors.
222
+ - `KeyError` should however not be raised except in the specific and rare case of implementing a `ta.Mapping` or
223
+ direct equivalent. For example, a `UserService` `get_user` method should raise a `UserNotFoundError`, not
224
+ `KeyError`, when a given user is not found.
225
+
226
+
227
+ ### Type Annotation
228
+
229
+ - Type annotate functions and class fields wherever possible, even if it is simply `ta.Any`, but use the most specific
230
+ annotation feasible.
231
+ - Lack of type annotation is an explicit choice communicating that that particular code cannot or should not be
232
+ statically typed (usually because it is particularly dynamic).
233
+ - Almost always, if one class field or function parameter is annotated, all fields / parameters / return values
234
+ should be.
235
+ - Return value annotations should be included on most magic methods like `__init__` and `__hash__`, but trickier
236
+ ones like `__exit__` and `__eq__` may be omitted.
237
+ - An exception to this is test code - in general don't bother type annotating test code, and in fact avoid test
238
+ function parameter annotations due to the dynamic nature of pytest fixtures.
239
+ - Use PEP-585 style annotations for builtin types - use `list[int]` instead of `ta.List[int]`, and `int | None`
240
+ instead of `ta.Optional[int]`.
241
+ - Use `typing` aliases for non-builtin types - use `ta.Sequence[int]` instead of `collections.abc.Sequence[int]`.
242
+ - Prefer to accept immutable, less-specific types - a function should likely use a `ta.Sequence[int]` parameter rather
243
+ than a `list[int]`. Use `ta.AbstractSet` over `set` and `frozenset`, and use `ta.Mapping` over `dict`, accordingly.
244
+ - When returning values, prefer to use the full type if the caller 'owns' the value, and use a less-specific, usually
245
+ immutable type when the caller does not. For example, a utility function filtering out odd numbers from a
246
+ `ta.Iterable[int]` can return a new `list[int]`, but a getter property on a class exposing some internal set of
247
+ integers should probably return a `ta.AbstractSet[int]` rather than a `set[int]`.
248
+ - Don't avoid `ta.Generic` and type parameters where it makes sense, but usually annotating something as a superclass
249
+ will suffice. When present in a class definition, `ta.Generic` should be the last class in the base class list.
250
+ - Do **NOT** use PEP-695 style type parameter syntax yet:
251
+ - Continue to declare `ta.TypeVar`'s explicitly at the top of the module.
252
+ - Continue to declare type aliases as global variables (whose own type is annotated as `ta.TypeAlias`). For example,
253
+ do `IntList: ta.TypeAlias = list[int]`, not `type IntList = list[int]`.
254
+ - Note that in \[**lite**\] code, there is no `ta.TypeAlias` yet (as it was added in 3.10). In lite code, suffix
255
+ the line with `# ta.TypeAlias`. Additionally, type aliases in lite code **must be kept on a single line**. This
256
+ restriction does not apply to standard code.
257
+ - Rationale: lite code is written to be 'amalgamated' - stitched together into a single python file - in which
258
+ case type aliases are relocated to the top of the file **and globally deduplicated**. As such each line of type
259
+ alias must be self-contained.
260
+ - Rationale: the `type` statement produces radically different and incompatible reflective behavior at runtime, and
261
+ in general tools still struggle with the new syntax.
262
+
263
+
264
+ ### Comments
265
+
266
+ - Avoid unnecessary and frivolous comments. Most semantic meaning should be able to be inferred from package / module
267
+ / type / method / function / parameter names and annotations. For example a function like
268
+ `def add_two_floats(x: float, y: float) -> float:` does not need a docstring or comments.
269
+ - Do not repeat typing information in function docstrings. In general function and parameter names and types should be
270
+ clear enough to not require explicitly listing them in docstrings. Do not use google-style or equivalent docstrings.
271
+ - Both opening and closing docstring triple-quotes should be alone on their own dedicated line *unless* the entire
272
+ docstring (including triple-quotes and indentation) fits on a single 120-column wide line.
273
+ - All docstrings should be followed by a blank line.
274
+ - Reserve inline comments for 'surprising' or dangerous things, such as invariants which must be maintained. A comment
275
+ like `self._ensure_user_exists() # ensure user exists` is worthless, but a comment like
276
+ `self._ensure_user_exists() # safe because we already hold the user lock` is valuable.
277
+
278
+
279
+ ### Documentation
280
+
281
+ - Documentation should be written in markdown, and should have a general maximum line width of 120 characters.
282
+ - Substantial packages should have a `README.md` file at the root of the package directory outlining the package's
283
+ purpose, usage, and high level architecture. These files are automatically included in distributions as resources
284
+ for end-users.
285
+ - This is however a work in progress ☺️.
286
+
287
+
288
+ ### Tests
289
+
290
+ - As above, write tests in pytest-style.
291
+ - Use raw assertions liberally in tests, and use pytest utilities like `pytest.raises`.
292
+ - Use fixtures and other advanced pytest features sparingly. Prefer to simply instantiate test data rather than wrap
293
+ it in a fixture.
294
+ - In \[**lite**\] test code the `unittest` package must be used instead of `pytest` because lite tests are run in
295
+ venvs with no external dependencies present.
296
+ - Be sure async tests are put in a `IsolatedAsyncioTestCase` subclass.
297
+ - It is equally fine to use both bare `assert` statements and `unittest` assert helpers like `assertCountEqual`.
298
+ - In general, prefer to write tests in a way that they can be run in parallel.
299
+ - Avoid mocks - prefer to structure code such that a 'simple' but still functioning implementation of an interface can
300
+ be used where a mock would otherwise. For example, for a some `UserService` interface with an `add_user` method, for
301
+ which a `RemoteUserService` would usually make a remote service call, prefer to implement a `DictUserService` class
302
+ with an `add_user` method such that it actually stores the added user in a dictionary on the instance.
303
+ - In practice these are usually useful to have outside of test code as default implementations anyway!
304
+ - These are often called 'fakes' but the term is avoided to emphasize their general non-test utility.
305
+ - Strongly avoid monkeypatching anything. Ideally code should be structured to allow more graceful means of
306
+ instrumentation and fault injection (e.g. via alternative interface implementations).
307
+ - Occasional unavoidable exceptions exist, such as being forced to patch an external dep, or when doing fault
308
+ injection that's too fine-grained to justify interface decomposition.
309
+ - An ideal to aim for is a test suite reproducing all realistic (or encountered) failures at each individual IO and
310
+ synchronization point.
311
+ - With multiple concurrent actors this may be achieved trhough 'lock-step' execution: with for example 2 related
312
+ actors running concurrently which encounter a shared point of synchronization, run a test twice, once with the
313
+ first actor running first, and once with the second actor running first.
314
+ - Do **not** use 'sleep' to simulate lock-step execution, timeouts, or other test conditions. Tests should strive to
315
+ deterministically complete as quickly as possible via explicit synchronization.
316
+
317
+
318
+ ### Runtime
319
+
320
+ - Unless forced to through interaction with external code, do not use environment variables for anything.
321
+ Configuration should be injected, usually as keyword-only class constructor arguments, and usually in the form of
322
+ dataclasses or `ta.NewType`s.
323
+ - Outside of test code, *NEVER* use `__file__` - never assume python code is running in `.py` files on a filesystem.
324
+ (In general, do not even assume to have a readable filesystem). Write code compatible with zipfile python dists and
325
+ pyoxidizer in which there is no `__file__`. Access resources via `lang.get_package_resources` /
326
+ `lang.get_relative_resources`.
327
+ - In general, with very rare exception, 'everything (that does IO) needs a timeout', but the default may be large
328
+ enough to never be realistically hit in practice (think 5m for interactive work, 1h for background work).
329
+ - By default all pytests already run with a standard timeout.
330
+
331
+
332
+ ### C Extensions
333
+
334
+ - C extensions use C11 and C++ extensions use C++20.
335
+ - In general prefer to write native extensions in C++.
336
+ - Use the C++ standard library liberally, but not 'excessively' lol. Write more 'C-style' code when interfacing with
337
+ CPython.
338
+ - C/C++ extensions should have `// @omlish-cext` as their first line, and will thereafter be automatically built and
339
+ packaged by existing codebase machinery.
340
+ - C/C++ extensions should be kept to a single, self-contained source file - do write new header files.
341
+ - C++ source files use the `.cc` extension, and C++ header files use the `.hh` extension.
342
+ - Native extensions *must* use PEP-489 style multi-phase extension initialization (`PyModuleDef_Init`).
343
+ - Modules should mark themselves `Py_MOD_GIL_NOT_USED` and `Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED` as applicable.
344
+ Modules should strive to be written to support both if at all possible.
345
+ - See `omdev/cexts/_boilerplate.cc` for a simple C++ extension template.
omlish/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # Overview
2
+
3
+ Core utilities and foundational code. It's relatively large but completely self-contained, and has **no required
4
+ dependencies of any kind**.
5
+
6
+ # Notable packages
7
+
8
+ - **[lang](https://github.com/wrmsr/omlish/blob/master/omlish/lang)** - The standard library of this standard library.
9
+ Usually imported as a whole (`from omlish import lang`), it contains an array of general purpose utilities used
10
+ practically everywhere. It is kept relatively lightweight: its heaviest import is stdlib dataclasses and its
11
+ transitives. Some of its contents include:
12
+
13
+ - **[cached](https://github.com/wrmsr/omlish/blob/master/omlish/lang/cached)** - The standard `cached_function` /
14
+ `cached_property` tools, which are more capable than
15
+ [`functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache).
16
+ - **[imports](https://github.com/wrmsr/omlish/blob/master/omlish/lang/imports.py)** - Import tools like:
17
+ - `proxy_import` - For late-loaded imports.
18
+ - `proxy_init` - For late-loaded module globals.
19
+ - `auto_proxy_init` - For automatic late-loaded package exports.
20
+ - **[classes](https://github.com/wrmsr/omlish/blob/master/omlish/lang/classes)** - Class tools and bases, such as
21
+ `Abstract` (which checks at subclass definition not instantiation), `Sealed` / `PackageSealed`, and `Final`.
22
+ - **[maybes](https://github.com/wrmsr/omlish/blob/master/omlish/lite/maybes.py)** - A simple, nestable formalization
23
+ of the presence or absence of an object, as in [many](https://en.cppreference.com/w/cpp/utility/optional)
24
+ [other](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
25
+ [languages](https://doc.rust-lang.org/std/option/).
26
+ - **[maysync](https://github.com/wrmsr/omlish/blob/master/omlish/lite/maysync.py)** - A lightweight means of sharing
27
+ code between sync and async contexts, eliminating the need for maintaining sync and async versions of functions.
28
+
29
+ - **[bootstrap](https://github.com/wrmsr/omlish/blob/master/omlish/bootstrap)** - A centralized, configurable,
30
+ all-in-one collection of various process-initialization minutiae like resource limiting, profiling, remote debugging,
31
+ log configuration, environment variables, et cetera. Usable as a context manager or via its
32
+ [cli](https://github.com/wrmsr/omlish/blob/master/omlish/bootstrap/main.py).
33
+
34
+ - **[collections](https://github.com/wrmsr/omlish/blob/master/omlish/collections)** - A handful of collection utilities
35
+ and simple implementations, including:
36
+
37
+ - **[cache](https://github.com/wrmsr/omlish/blob/master/omlish/collections/cache)** - A configurable LRU / LFU cache
38
+ with options like ttl and max size / weight.
39
+ - **[hasheq](https://github.com/wrmsr/omlish/blob/master/omlish/collections/hasheq.py)** - A dict taking an external
40
+ `__hash__` / `__eq__` implementation.
41
+ - **[identity](https://github.com/wrmsr/omlish/blob/master/omlish/collections/identity.py)** - Identity-keyed
42
+ collections.
43
+ - **[sorted](https://github.com/wrmsr/omlish/blob/master/omlish/collections/sorted)** - Interfaces for value-sorted
44
+ collections and key-sorted mappings, and a simple but correct skiplist-backed implementation.
45
+ - **[persistent](https://github.com/wrmsr/omlish/blob/master/omlish/collections/persistent)** - Interfaces for
46
+ [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure) maps, and a simple but correct treap-backed
47
+ implementation.
48
+
49
+ - **[dataclasses](https://github.com/wrmsr/omlish/blob/master/omlish/dataclasses)** - A fully-compatible
50
+ reimplementation of stdlib [dataclasses](https://docs.python.org/3/library/dataclasses.html) with numerous
51
+ enhancements and additional features. The
52
+ [full stdlib test suite](https://github.com/wrmsr/omlish/blob/master/omlish/dataclasses/tests/cpython) is run against
53
+ it ensuring compatibility - they *are* dataclasses. Current enhancements include:
54
+
55
+ - Simple field coercion and validation.
56
+ - Any number of `@dc.init` or `@dc.validate` methods, not just a central `__post_init__`.
57
+ - Optional generic type parameter substitution in generated `__init__` methods, enabling accurate reflection.
58
+ - An optional [metaclass](https://github.com/wrmsr/omlish/blob/master/omlish/dataclasses/metaclass) which removes the
59
+ need for re-decorating subclasses (with support for inheritance of dataclass parameters like `frozen`), and some
60
+ basic [base classes](https://github.com/wrmsr/omlish/blob/master/omlish/dataclasses/metaclass/bases.py).
61
+ - Support for ahead-of-time / build-time code generation, significantly reducing import times.
62
+
63
+ The stdlib-equivalent api is exported in such a way as to appear to be direct aliases for the stdlib api itself,
64
+ simplifying tool support.
65
+
66
+ - **[dispatch](https://github.com/wrmsr/omlish/blob/master/omlish/dispatch)** - A beefed-up version of
67
+ [functools.singledispatch](https://docs.python.org/3/library/functools.html#functools.singledispatch), most notably
68
+ supporting MRO-honoring method impl dispatch.
69
+
70
+ - **[formats](https://github.com/wrmsr/omlish/blob/master/omlish/formats)** - Tools for various data formats, including:
71
+
72
+ - **[json](https://github.com/wrmsr/omlish/blob/master/omlish/formats/json)** - Tools for json, including abstraction
73
+ over various backends and a self-contained streaming / incremental parser.
74
+ - **[json5](https://github.com/wrmsr/omlish/blob/master/omlish/formats/json5)** - A self-contained and tested
75
+ [Json5](https://json5.org/) parser.
76
+ - **[toml](https://github.com/wrmsr/omlish/blob/master/omlish/formats/toml)** - Toml tools, including a
77
+ [lite](#lite-code) version of the stdlib parser (for use in older pythons).
78
+
79
+ - **[http](https://github.com/wrmsr/omlish/blob/master/omlish/http)** - HTTP code, including:
80
+
81
+ - **[clients](https://github.com/wrmsr/omlish/blob/master/omlish/http/clients)** - An abstraction over HTTP clients,
82
+ with urllib and httpx implementations.
83
+ - **[coro](https://github.com/wrmsr/omlish/blob/master/omlish/http/coro)** - Coroutine /
84
+ [sans-io](https://sans-io.readthedocs.io/) style reformulation of some stdlib http machinery - namely `http.server`
85
+ (and soon `http.client`). This style of code can run the same in sync, async, or
86
+ [any](https://docs.python.org/3/library/selectors.html)
87
+ [other](https://github.com/wrmsr/omlish/blob/master/omlish/asyncs/bluelet) context.
88
+
89
+ - **[inject](https://github.com/wrmsr/omlish/blob/master/omlish/inject)** - A
90
+ [guice](https://github.com/google/guice)-style dependency injector.
91
+
92
+ - **[io](https://github.com/wrmsr/omlish/blob/master/omlish/io)** - IO tools, including:
93
+
94
+ - **[compress](https://github.com/wrmsr/omlish/blob/master/omlish/io/compress)** - Abstraction over various
95
+ compression schemes, with particular attention to incremental operation. For example it includes
96
+ [an incremental reformulation of stdlib's gzip](https://github.com/wrmsr/omlish/blob/master/omlish/io/compress/gzip.py).
97
+ - **[coro](https://github.com/wrmsr/omlish/blob/master/omlish/io/coro)** - Utilities for coroutine / sans-io style
98
+ code.
99
+ - **[fdio](https://github.com/wrmsr/omlish/blob/master/omlish/io/fdio)** - An implementation of classic
100
+ [selector](https://docs.python.org/3/library/selectors.html)-style IO dispatch, akin to the deprecated
101
+ [asyncore](https://docs.python.org/3.11/library/asyncore.html). While more modern asyncio style code is generally
102
+ preferred, it nearly always involves
103
+ [background threads](https://github.com/python/cpython/blob/95d9dea1c4ed1b1de80074b74301cee0b38d5541/Lib/asyncio/unix_events.py#L1349)
104
+ making it [unsuitable for forking processes](https://rachelbythebay.com/w/2011/06/07/forked/) like
105
+ [process supervisors](https://github.com/wrmsr/omlish/blob/master/ominfra/supervisor).
106
+
107
+ - **[jmespath](https://github.com/wrmsr/omlish/blob/master/omlish/specs/jmespath)** - A vendoring of
108
+ [jmespath community edition](https://github.com/jmespath-community/python-jmespath), modernized and adapted to this
109
+ codebase.
110
+
111
+ - **[marshal](https://github.com/wrmsr/omlish/blob/master/omlish/marshal)** - A
112
+ [jackson](https://github.com/FasterXML/jackson)-style serde system.
113
+
114
+ - **[manifests](https://github.com/wrmsr/omlish/blob/master/omlish/manifests)** - A system for sharing lightweight
115
+ metadata within / across codebases.
116
+
117
+ - **[reflect](https://github.com/wrmsr/omlish/blob/master/omlish/reflect)** - Reflection utilities, including primarily
118
+ a formalization of stdlib type annotations for use at runtime, decoupled from stdlib impl detail. Keeping this working
119
+ is notoriously difficult across python versions (one of the primary reasons for only supporting 3.13+).
120
+
121
+ - **[sql](https://github.com/wrmsr/omlish/blob/master/omlish/sql)** - A collection of SQL utilities, including:
122
+
123
+ - **[api](https://github.com/wrmsr/omlish/blob/master/omlish/sql/api)** - An abstracted api for SQL interaction, with
124
+ support for dbapi compatible drivers (and a SQLAlchemy adapter).
125
+ - **[queries](https://github.com/wrmsr/omlish/blob/master/omlish/sql/queries)** - A SQL query builder with a fluent
126
+ interface.
127
+ - **[alchemy](https://github.com/wrmsr/omlish/blob/master/omlish/sql/alchemy)** - SQLAlchemy utilities. The codebase
128
+ has moved away from SQLAlchemy in favor of its own internal SQL api, but it will likely still remain as an optional
129
+ dep for the api adapter.
130
+
131
+ - **[testing](https://github.com/wrmsr/omlish/blob/master/omlish/testing)** - Test - primarily pytest - helpers,
132
+ including:
133
+
134
+ - **['harness'](https://github.com/wrmsr/omlish/blob/master/omlish/testing/pytest/inject/harness.py)** - An all-in-one
135
+ fixture marrying it to the codebase's dependency injector.
136
+ - **[plugins/async](https://github.com/wrmsr/omlish/blob/master/omlish/testing/pytest/plugins/asyncs)** - An in-house
137
+ async-backend abstraction plugin, capable of handling all of asyncio / trio / trio-asyncio /
138
+ *any-future-event-loop-impl* without having multiple fighting plugins (*[I know, I know](https://xkcd.com/927/)*).
139
+ - **[plugins](https://github.com/wrmsr/omlish/blob/master/omlish/testing/pytest/plugins)** - Various other plugins.
140
+
141
+ - **[typedvalues](https://github.com/wrmsr/omlish/blob/master/omlish/typedvalues)** - A little toolkit around 'boxed'
142
+ values, whose 'box' types convey more information than the bare values themselves. A rebellion against kwargs / env
143
+ vars / giant config objects: instead of `foo(bar=1, baz=2)`, you do `foo(Bar(1), Baz(2))`.
144
+
145
+ - **[lite](https://github.com/wrmsr/omlish/blob/master/omlish/lite)** - The standard library of 'lite' code. This is the
146
+ only package beneath `lang`, and parts of it are re-exported by it for deduplication. On top of miscellaneous
147
+ utilities it contains a handful of independent, self-contained, significantly simplified 'lite' equivalents of some
148
+ major core packages:
149
+
150
+ - **[lite/inject.py](https://github.com/wrmsr/omlish/blob/master/omlish/lite/inject.py)** - The lite injector, which
151
+ is more conservative with features and reflection than the core injector. The codebase's
152
+ [MiniGuice](https://github.com/google/guice/commit/70248eafa90cd70a68b293763e53f6aec656e73c).
153
+ - **[lite/marshal.py](https://github.com/wrmsr/omlish/blob/master/omlish/lite/marshal.py)** - The lite marshalling
154
+ system, which is a classic canned setup of simple type-specific 2-method classes and limited generic handling.
155
+
156
+ # Lite code
157
+
158
+ A subset of this codebase is written in a 'lite' style (non-'lite' code is referred to as *standard* code). While
159
+ standard code is written for python 3.13+, 'lite' code is written for 3.8+, and is written in a style conducive to
160
+ [amalgamation](https://github.com/wrmsr/omlish/blob/master/omdev#amalgamation) in which multiple python source files are
161
+ stitched together into one single self-contained python script.
162
+
163
+ Code written in this style has notable differences from standard code, including (but not limited to):
164
+
165
+ - No name mangling is done in amalgamation, which means (among other things) that code must be written expecting to be
166
+ all dumped into the same giant namespace. Where a standard class might be
167
+ [`omlish.inject.keys.Key`](https://github.com/wrmsr/omlish/blob/master/omlish/inject/keys.py), a lite equivalent might
168
+ be [`omlish.lite.inject.InjectorKey`](https://github.com/wrmsr/omlish/blob/master/omlish/lite/inject.py).
169
+ - All internal imports `import` each individual item out of modules rather than importing the modules and referencing
170
+ their contents. Where standard code would `from .. import x; x.y`, lite code would `from ..x import y; y`. As a result
171
+ there are frequently 'api' non-instantiated namespace classes serving the purpose of modules - just handy bags of
172
+ stuff with shortened names.
173
+ - As lite code is tested in 3.8+ but core code requires 3.13+, packages containing lite code can't import anything
174
+ standard in their (and their ancestors') `__init__.py`'s. Furthermore, `__init__.py` files are omitted outright in
175
+ amalgamation, so they effectively must be empty in any package containing any lite code. As a result there are
176
+ frequently [`all.py`](https://github.com/wrmsr/omlish/blob/master/omlish/configs/all.py) files in mixed-lite packages
177
+ which serve the purpose of `__init__.py` for standard usage - where importing standard packages from standard code
178
+ would be done via `from .. import lang`, importing mixed-lite packages from standard code would be done via
179
+ `from ..configs import all as cfgs`.
180
+
181
+ # Dependencies
182
+
183
+ This library has no required dependencies of any kind, but there are some optional integrations - see
184
+ [`__about__.py`](https://github.com/wrmsr/omlish/blob/master/omlish/__about__.py) for a full list, but some specific
185
+ examples are:
186
+
187
+ - **asttokens / executing** - For getting runtime source representations of function call arguments, an optional
188
+ capability of [check](https://github.com/wrmsr/omlish/blob/master/omlish/check.py).
189
+ - **anyio** - While lite code must use only asyncio, non-trivial async standard code prefers to be written to anyio.
190
+ - **pytest** - What is used for all standard testing - as lite code has no dependencies of any kind its testing uses
191
+ stdlib's [unittest](https://docs.python.org/3/library/unittest.html).
192
+ - **sqlalchemy** - The codebase has migrated away from SQLAlchemy in favor of the internal api but it retains it as an
193
+ optional dep to support adapting the internal api to it.
194
+
195
+ Additionally, some catchall dep categories include:
196
+
197
+ - **compression** - Various preferred compression backends like lz4, python-snappy, zstandard, and brotli.
198
+ - **formats** - Various preferred data format backends like orjson/ujson, pyyaml, cbor2, and cloudpickle.
199
+ - **sql drivers** - Various preferred and tested sql drivers.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev484'
2
- __revision__ = '7c8d11bd7ee674238104cc5940ee4971dbf70a29'
1
+ __version__ = '0.0.0.dev506'
2
+ __revision__ = '3d7c2a9a417108012b5cf11d20ed3e2eef483b6d'
3
3
 
4
4
 
5
5
  #
@@ -60,7 +60,7 @@ class Project(ProjectBase):
60
60
  'asttokens ~= 3.0',
61
61
  'executing ~= 2.2',
62
62
 
63
- 'psutil ~= 7.1',
63
+ 'psutil ~= 7.2',
64
64
  ],
65
65
 
66
66
  'formats': [
@@ -69,7 +69,7 @@ class Project(ProjectBase):
69
69
 
70
70
  'pyyaml ~= 6.0',
71
71
 
72
- 'cbor2 ~= 5.7',
72
+ 'cbor2 ~= 5.8',
73
73
 
74
74
  'cloudpickle ~= 3.1',
75
75
  ],
@@ -99,8 +99,10 @@ class Project(ProjectBase):
99
99
  # 'mysql-connector-python ~= 9.5',
100
100
  # 'mysqlclient ~= 2.2',
101
101
 
102
+ 'snowflake-connector-python ~= 4.2',
103
+
102
104
  'aiomysql ~= 0.3',
103
- 'aiosqlite ~= 0.21',
105
+ 'aiosqlite ~= 0.22',
104
106
  'asyncpg ~= 0.31',
105
107
 
106
108
  'apsw ~= 3.51',
@@ -178,8 +180,13 @@ class SetuptoolsBase:
178
180
 
179
181
  '.omlish-manifests.json',
180
182
 
183
+ 'README',
184
+ 'README.md',
185
+
181
186
  'LICENSE',
182
187
  'LICENSE.txt',
188
+
189
+ 'AUTHORS',
183
190
  ],
184
191
  }
185
192