ddutils 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- ddutils-0.2.0/README.md → ddutils-0.2.2/PKG-INFO +82 -20
- ddutils-0.2.0/PKG-INFO → ddutils-0.2.2/README.md +64 -32
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/class_helpers.py +21 -1
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/scoped_registry.py +50 -8
- {ddutils-0.2.0 → ddutils-0.2.2}/pyproject.toml +9 -1
- {ddutils-0.2.0 → ddutils-0.2.2}/pyproject.toml.orig +10 -1
- {ddutils-0.2.0 → ddutils-0.2.2}/LICENSE +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/__init__.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/annotation_helpers.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/convertors.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/datetime_helpers.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/function_exceptions_extractor.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/function_helpers.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/module_getter.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/object_getter.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/py.typed +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/safe_decorators.py +0 -0
- {ddutils-0.2.0 → ddutils-0.2.2}/ddutils/sequence_helpers.py +0 -0
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddutils
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Domain Driven Utils Library
|
|
5
|
+
Keywords: python,ddutils
|
|
6
|
+
Author: davyddd
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.11, <3.15
|
|
16
|
+
Project-URL: Repository, https://github.com/davyddd/ddutils
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
1
19
|
# DDUtils
|
|
2
20
|
|
|
3
21
|
[](https://pypi.python.org/pypi/ddutils)
|
|
@@ -173,6 +191,31 @@ get_origin_class_of_method(C, 'method') # <class 'C'>
|
|
|
173
191
|
get_origin_class_of_method(B, 'method') # <class 'A'>
|
|
174
192
|
```
|
|
175
193
|
|
|
194
|
+
### get_generic_base_argument
|
|
195
|
+
|
|
196
|
+
Returns the type argument a class bound to a generic base when subclassing it. `None` when the base was not
|
|
197
|
+
subscripted, the `TypeVar` itself when the subclass is still generic.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from typing import Generic, TypeVar
|
|
201
|
+
|
|
202
|
+
from ddutils.class_helpers import get_generic_base_argument
|
|
203
|
+
|
|
204
|
+
T = TypeVar('T')
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Repository(Generic[T]):
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class UserRepository(Repository[User]):
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
get_generic_base_argument(UserRepository, Repository) # <class 'User'>
|
|
216
|
+
get_generic_base_argument(Repository, Repository) # None
|
|
217
|
+
```
|
|
218
|
+
|
|
176
219
|
## Function Helpers
|
|
177
220
|
|
|
178
221
|
Tools for creating and modifying Python functions at runtime.
|
|
@@ -321,9 +364,8 @@ A registry that keeps one object instance per scope and manages its lifetime.
|
|
|
321
364
|
|
|
322
365
|
### ScopedRegistry
|
|
323
366
|
|
|
324
|
-
Keeps one object per scope.
|
|
325
|
-
|
|
326
|
-
by the tasks spawned inside it).
|
|
367
|
+
Keeps one object per scope. `scope_func` decides what the current scope is and returns its key; `None` means
|
|
368
|
+
"no current scope" and makes every method raise `RuntimeError`.
|
|
327
369
|
|
|
328
370
|
`scope()` is the entry point into a scope: it creates the object on enter and removes it on exit, calling
|
|
329
371
|
the optional destructor exactly once. Nested `scope()` blocks join the existing object and leave it alone.
|
|
@@ -331,7 +373,6 @@ Concurrent first accesses with the same key call `create_func` only once.
|
|
|
331
373
|
|
|
332
374
|
```python
|
|
333
375
|
import asyncio
|
|
334
|
-
from contextvars import ContextVar
|
|
335
376
|
|
|
336
377
|
from ddutils.scoped_registry import ScopedRegistry
|
|
337
378
|
|
|
@@ -348,32 +389,53 @@ async def handle_request():
|
|
|
348
389
|
await session.execute('select ...')
|
|
349
390
|
async with session_registry.scope() as same_session: # nested block joins
|
|
350
391
|
...
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Lower-level methods are available when a context manager does not fit:
|
|
395
|
+
|
|
396
|
+
- `get()` returns the current object or `None`;
|
|
397
|
+
- `set(**kwargs)` returns the existing object or creates it. Whoever calls it takes over the cleanup that
|
|
398
|
+
`scope()` does on exit and must call `clear()` when the object is no longer needed, if it ever is: a
|
|
399
|
+
per-process client may live until the process ends;
|
|
400
|
+
- `clear()` removes the object of the current scope and calls the destructor.
|
|
401
|
+
|
|
402
|
+
#### Scopes kept in a ContextVar
|
|
403
|
+
|
|
404
|
+
A `scope_func` that declares a `token` parameter receives a freshly generated token when `scope()` is entered
|
|
405
|
+
and `None` on every other access. This is for a scope of one execution (an HTTP request, an actor run) rather
|
|
406
|
+
than one task: the function stores the token in a `ContextVar`, so coroutines spawned inside the block
|
|
407
|
+
(`gather`, `TaskGroup`, `create_task`) inherit it and share the object with the parent, while outside of
|
|
408
|
+
`scope()` there is no key and every method raises.
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from contextvars import ContextVar
|
|
412
|
+
|
|
413
|
+
execution_key: ContextVar[str | None] = ContextVar('execution_key', default=None)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def execution_scope(token: str | None) -> str | None:
|
|
417
|
+
key = execution_key.get()
|
|
418
|
+
if key is None and token is not None: # entering scope() outside of an execution: start one
|
|
419
|
+
execution_key.set(token)
|
|
420
|
+
key = token
|
|
421
|
+
return key
|
|
351
422
|
|
|
352
423
|
|
|
353
424
|
async def create_log_properties(**kwargs): ...
|
|
354
425
|
|
|
355
426
|
|
|
356
|
-
|
|
357
|
-
request_key: ContextVar[object | None] = ContextVar('request_key', default=None)
|
|
358
|
-
log_registry = ScopedRegistry(create_func=create_log_properties, scope_func=request_key.get)
|
|
427
|
+
log_registry = ScopedRegistry(create_func=create_log_properties, scope_func=execution_scope)
|
|
359
428
|
|
|
360
429
|
|
|
361
430
|
async def middleware(headers):
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
async with log_registry.scope(headers=headers) as log_properties:
|
|
365
|
-
await asyncio.gather(work(), work()) # both see the same log_properties via log_registry.get()
|
|
366
|
-
finally:
|
|
367
|
-
request_key.reset(token)
|
|
431
|
+
async with log_registry.scope(headers=headers) as log_properties:
|
|
432
|
+
await asyncio.gather(work(), work()) # both see the same log_properties via log_registry.get()
|
|
368
433
|
```
|
|
369
434
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
- `clear()` removes the object of the current scope and calls the destructor.
|
|
375
|
-
|
|
376
|
-
`scope_func` must return a hashable key; if it returns `None` or an unhashable value, every method raises `RuntimeError`.
|
|
435
|
+
Reads never start a scope: a `get()` from a long-living context (a startup log in the server's main task)
|
|
436
|
+
that planted a key would be inherited by every request task, making all requests share one object. For the
|
|
437
|
+
same reason the key is not reset when the block ends, so `scope()` must be entered in a task that does not
|
|
438
|
+
outlive the block (a request handler, an actor run).
|
|
377
439
|
|
|
378
440
|
## Function Exception Extraction
|
|
379
441
|
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ddutils
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Domain Driven Utils Library
|
|
5
|
-
Keywords: python,ddutils
|
|
6
|
-
Author: davyddd
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Python: >=3.11, <3.15
|
|
10
|
-
Project-URL: Repository, https://github.com/davyddd/ddutils
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
|
|
13
1
|
# DDUtils
|
|
14
2
|
|
|
15
3
|
[](https://pypi.python.org/pypi/ddutils)
|
|
@@ -185,6 +173,31 @@ get_origin_class_of_method(C, 'method') # <class 'C'>
|
|
|
185
173
|
get_origin_class_of_method(B, 'method') # <class 'A'>
|
|
186
174
|
```
|
|
187
175
|
|
|
176
|
+
### get_generic_base_argument
|
|
177
|
+
|
|
178
|
+
Returns the type argument a class bound to a generic base when subclassing it. `None` when the base was not
|
|
179
|
+
subscripted, the `TypeVar` itself when the subclass is still generic.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from typing import Generic, TypeVar
|
|
183
|
+
|
|
184
|
+
from ddutils.class_helpers import get_generic_base_argument
|
|
185
|
+
|
|
186
|
+
T = TypeVar('T')
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class Repository(Generic[T]):
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class UserRepository(Repository[User]):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
get_generic_base_argument(UserRepository, Repository) # <class 'User'>
|
|
198
|
+
get_generic_base_argument(Repository, Repository) # None
|
|
199
|
+
```
|
|
200
|
+
|
|
188
201
|
## Function Helpers
|
|
189
202
|
|
|
190
203
|
Tools for creating and modifying Python functions at runtime.
|
|
@@ -333,9 +346,8 @@ A registry that keeps one object instance per scope and manages its lifetime.
|
|
|
333
346
|
|
|
334
347
|
### ScopedRegistry
|
|
335
348
|
|
|
336
|
-
Keeps one object per scope.
|
|
337
|
-
|
|
338
|
-
by the tasks spawned inside it).
|
|
349
|
+
Keeps one object per scope. `scope_func` decides what the current scope is and returns its key; `None` means
|
|
350
|
+
"no current scope" and makes every method raise `RuntimeError`.
|
|
339
351
|
|
|
340
352
|
`scope()` is the entry point into a scope: it creates the object on enter and removes it on exit, calling
|
|
341
353
|
the optional destructor exactly once. Nested `scope()` blocks join the existing object and leave it alone.
|
|
@@ -343,7 +355,6 @@ Concurrent first accesses with the same key call `create_func` only once.
|
|
|
343
355
|
|
|
344
356
|
```python
|
|
345
357
|
import asyncio
|
|
346
|
-
from contextvars import ContextVar
|
|
347
358
|
|
|
348
359
|
from ddutils.scoped_registry import ScopedRegistry
|
|
349
360
|
|
|
@@ -360,32 +371,53 @@ async def handle_request():
|
|
|
360
371
|
await session.execute('select ...')
|
|
361
372
|
async with session_registry.scope() as same_session: # nested block joins
|
|
362
373
|
...
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Lower-level methods are available when a context manager does not fit:
|
|
377
|
+
|
|
378
|
+
- `get()` returns the current object or `None`;
|
|
379
|
+
- `set(**kwargs)` returns the existing object or creates it. Whoever calls it takes over the cleanup that
|
|
380
|
+
`scope()` does on exit and must call `clear()` when the object is no longer needed, if it ever is: a
|
|
381
|
+
per-process client may live until the process ends;
|
|
382
|
+
- `clear()` removes the object of the current scope and calls the destructor.
|
|
383
|
+
|
|
384
|
+
#### Scopes kept in a ContextVar
|
|
385
|
+
|
|
386
|
+
A `scope_func` that declares a `token` parameter receives a freshly generated token when `scope()` is entered
|
|
387
|
+
and `None` on every other access. This is for a scope of one execution (an HTTP request, an actor run) rather
|
|
388
|
+
than one task: the function stores the token in a `ContextVar`, so coroutines spawned inside the block
|
|
389
|
+
(`gather`, `TaskGroup`, `create_task`) inherit it and share the object with the parent, while outside of
|
|
390
|
+
`scope()` there is no key and every method raises.
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
from contextvars import ContextVar
|
|
394
|
+
|
|
395
|
+
execution_key: ContextVar[str | None] = ContextVar('execution_key', default=None)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def execution_scope(token: str | None) -> str | None:
|
|
399
|
+
key = execution_key.get()
|
|
400
|
+
if key is None and token is not None: # entering scope() outside of an execution: start one
|
|
401
|
+
execution_key.set(token)
|
|
402
|
+
key = token
|
|
403
|
+
return key
|
|
363
404
|
|
|
364
405
|
|
|
365
406
|
async def create_log_properties(**kwargs): ...
|
|
366
407
|
|
|
367
408
|
|
|
368
|
-
|
|
369
|
-
request_key: ContextVar[object | None] = ContextVar('request_key', default=None)
|
|
370
|
-
log_registry = ScopedRegistry(create_func=create_log_properties, scope_func=request_key.get)
|
|
409
|
+
log_registry = ScopedRegistry(create_func=create_log_properties, scope_func=execution_scope)
|
|
371
410
|
|
|
372
411
|
|
|
373
412
|
async def middleware(headers):
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
async with log_registry.scope(headers=headers) as log_properties:
|
|
377
|
-
await asyncio.gather(work(), work()) # both see the same log_properties via log_registry.get()
|
|
378
|
-
finally:
|
|
379
|
-
request_key.reset(token)
|
|
413
|
+
async with log_registry.scope(headers=headers) as log_properties:
|
|
414
|
+
await asyncio.gather(work(), work()) # both see the same log_properties via log_registry.get()
|
|
380
415
|
```
|
|
381
416
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
- `clear()` removes the object of the current scope and calls the destructor.
|
|
387
|
-
|
|
388
|
-
`scope_func` must return a hashable key; if it returns `None` or an unhashable value, every method raises `RuntimeError`.
|
|
417
|
+
Reads never start a scope: a `get()` from a long-living context (a startup log in the server's main task)
|
|
418
|
+
that planted a key would be inherited by every request task, making all requests share one object. For the
|
|
419
|
+
same reason the key is not reset when the block ends, so `scope()` must be entered in a task that does not
|
|
420
|
+
outlive the block (a request handler, an actor run).
|
|
389
421
|
|
|
390
422
|
## Function Exception Extraction
|
|
391
423
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from typing import Any, TypeVar
|
|
3
|
+
from typing import Any, TypeVar, get_args, get_origin
|
|
4
4
|
|
|
5
5
|
_T = TypeVar('_T', bound=Any)
|
|
6
6
|
|
|
@@ -25,6 +25,26 @@ def get_origin_class_of_method(cls: Any, method_name: str) -> type | None:
|
|
|
25
25
|
return None
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def get_generic_base_argument(cls: type, generic_base: type, position: int = 0) -> Any | None:
|
|
29
|
+
"""
|
|
30
|
+
Return the type argument that `cls` bound to `generic_base` when subclassing it.
|
|
31
|
+
|
|
32
|
+
For `class ProfileCache(GenericCache[Profile])` this is `Profile`. Returns `None` when `cls` does not
|
|
33
|
+
subscript `generic_base` (`class Cache(GenericCache)`), and a `TypeVar` when the subclass is still
|
|
34
|
+
generic (`class Base[T](GenericCache[T])`), so callers can reject both cases in `__init_subclass__`.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
cls: The class to inspect.
|
|
38
|
+
generic_base: The generic base class whose argument to look up.
|
|
39
|
+
position: Index of the argument for bases with several type parameters.
|
|
40
|
+
"""
|
|
41
|
+
for base in getattr(cls, '__orig_bases__', ()):
|
|
42
|
+
if get_origin(base) is generic_base:
|
|
43
|
+
arguments = get_args(base)
|
|
44
|
+
return arguments[position] if position < len(arguments) else None
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
28
48
|
class classproperty: # noqa: N801
|
|
29
49
|
"""
|
|
30
50
|
A descriptor that defines a read-only property at the class level.
|
|
@@ -3,15 +3,42 @@ import inspect
|
|
|
3
3
|
from collections.abc import AsyncIterator, Awaitable, Callable, Hashable
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from typing import Any, Generic, TypeVar, get_args
|
|
6
|
+
from uuid import uuid4
|
|
6
7
|
|
|
7
8
|
_T = TypeVar('_T')
|
|
8
9
|
_CreateFuncType = Callable[..., _T] | Callable[..., Awaitable[_T]]
|
|
9
10
|
_ScopeKey = Hashable
|
|
10
|
-
_ScopeFuncType = Callable[
|
|
11
|
+
_ScopeFuncType = Callable[..., _ScopeKey | None]
|
|
12
|
+
# A `scope_func` that declares this parameter receives a freshly generated key when `scope()` is entered
|
|
13
|
+
# and `None` on every other access; functions without it are called with no arguments.
|
|
14
|
+
SCOPE_TOKEN_PARAMETER = 'token'
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class ScopedRegistry(Generic[_T]):
|
|
14
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
One value per scope.
|
|
20
|
+
|
|
21
|
+
`scope_func` decides what the current scope is and returns its key; `None` means "no current scope" and
|
|
22
|
+
makes the registry raise `RuntimeError`. Plain key functions take no arguments:
|
|
23
|
+
|
|
24
|
+
asyncio.current_task # one value per task, a child task does not see the parent's value
|
|
25
|
+
os.getpid # one value per process
|
|
26
|
+
|
|
27
|
+
A `scope_func` that declares a `token` parameter is meant for scopes kept in a `ContextVar`: on entering
|
|
28
|
+
`scope()` (or on `set()`) it receives a freshly generated token that it may store as the key of a new scope,
|
|
29
|
+
and on every other access (`get`, `clear`, a nested `scope()`) it receives `None` and must only read:
|
|
30
|
+
|
|
31
|
+
def execution_scope(token): # one value per execution (HTTP request, actor run),
|
|
32
|
+
key = execution_key.get() # inherited by the tasks spawned inside the block
|
|
33
|
+
if key is None and token is not None:
|
|
34
|
+
execution_key.set(token)
|
|
35
|
+
key = token
|
|
36
|
+
return key
|
|
37
|
+
|
|
38
|
+
A scope started this way is not reset when the block ends, so `scope()` must be entered in a task that does
|
|
39
|
+
not outlive the block (a request handler, an actor run); a key left in a long-living task would be
|
|
40
|
+
inherited by every task spawned from it afterwards.
|
|
41
|
+
"""
|
|
15
42
|
|
|
16
43
|
__slots__ = ('create_func', 'scope_func', 'registry', 'destructor_method_name', '_lock', '__orig_class__')
|
|
17
44
|
|
|
@@ -35,9 +62,17 @@ class ScopedRegistry(Generic[_T]):
|
|
|
35
62
|
except IndexError as e:
|
|
36
63
|
raise TypeError('Cannot determine generic type parameter') from e
|
|
37
64
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
if
|
|
65
|
+
def _get_scope_func_kwargs(self, token: str | None) -> dict[str, Any]:
|
|
66
|
+
kwargs = {}
|
|
67
|
+
if SCOPE_TOKEN_PARAMETER in inspect.signature(self.scope_func).parameters:
|
|
68
|
+
kwargs['token'] = token
|
|
69
|
+
return kwargs
|
|
70
|
+
|
|
71
|
+
def _get_scope_key(self, token: str | None = None) -> _ScopeKey:
|
|
72
|
+
key = self.scope_func(**self._get_scope_func_kwargs(token))
|
|
73
|
+
if key is None:
|
|
74
|
+
raise RuntimeError('There is no current scope')
|
|
75
|
+
if not isinstance(key, Hashable):
|
|
41
76
|
raise RuntimeError(f'scope_func must return a hashable scope key, got {key!r}')
|
|
42
77
|
return key
|
|
43
78
|
|
|
@@ -45,8 +80,8 @@ class ScopedRegistry(Generic[_T]):
|
|
|
45
80
|
return self.registry.get(self._get_scope_key())
|
|
46
81
|
|
|
47
82
|
async def _set(self, **kwargs: Any) -> tuple[_T, bool]:
|
|
48
|
-
"""Returns the value of the current scope and whether this call created it."""
|
|
49
|
-
key = self._get_scope_key()
|
|
83
|
+
"""Returns the value of the current scope and whether this call created it; may start a new scope."""
|
|
84
|
+
key = self._get_scope_key(uuid4().hex)
|
|
50
85
|
if key in self.registry:
|
|
51
86
|
return self.registry[key], False
|
|
52
87
|
|
|
@@ -58,7 +93,13 @@ class ScopedRegistry(Generic[_T]):
|
|
|
58
93
|
return self.registry[key], True
|
|
59
94
|
|
|
60
95
|
async def set(self, **kwargs: Any) -> _T:
|
|
61
|
-
"""
|
|
96
|
+
"""
|
|
97
|
+
Returns the value of the current scope, creating it with `create_func(**kwargs)` if needed.
|
|
98
|
+
|
|
99
|
+
Prefer `scope()`: it removes what it created on exit. Whoever calls `set()` directly takes over that
|
|
100
|
+
responsibility and must call `clear()` when the value is no longer needed (if it ever is: a per-process
|
|
101
|
+
client may live until the process ends). A `scope_func` with a `token` parameter may start a new scope here.
|
|
102
|
+
"""
|
|
62
103
|
value, _ = await self._set(**kwargs)
|
|
63
104
|
return value
|
|
64
105
|
|
|
@@ -76,6 +117,7 @@ class ScopedRegistry(Generic[_T]):
|
|
|
76
117
|
"""
|
|
77
118
|
Enters the current scope: joins the existing value or creates one. Only the block that created the
|
|
78
119
|
value removes it (and calls the destructor) on exit; nested blocks join and leave it alone.
|
|
120
|
+
A `scope_func` with a `token` parameter is offered a fresh token here and may start a new scope with it.
|
|
79
121
|
"""
|
|
80
122
|
value, created = await self._set(**kwargs)
|
|
81
123
|
try:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddutils"
|
|
3
3
|
description = "Domain Driven Utils Library"
|
|
4
|
-
version = "0.2.
|
|
4
|
+
version = "0.2.2"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
7
7
|
readme = "README.md"
|
|
@@ -10,6 +10,14 @@ keywords = [
|
|
|
10
10
|
"python",
|
|
11
11
|
"ddutils",
|
|
12
12
|
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
13
21
|
dependencies = []
|
|
14
22
|
|
|
15
23
|
[[project.authors]]
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddutils"
|
|
3
3
|
description = "Domain Driven Utils Library"
|
|
4
|
-
version = "0.2.
|
|
4
|
+
version = "0.2.2"
|
|
5
5
|
authors = [{ name = "davyddd" }]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.11,<3.15"
|
|
10
10
|
keywords = ["python", "ddutils"]
|
|
11
|
+
# uv_build does not derive these from requires-python (poetry did); PyPI and the pyversions badge read them
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
11
20
|
dependencies = []
|
|
12
21
|
|
|
13
22
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|