datashare-python 0.11.2__py3-none-any.whl → 0.11.4__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.
- datashare_python/utils.py +90 -40
- datashare_python/worker-template.tar.gz +0 -0
- {datashare_python-0.11.2.dist-info → datashare_python-0.11.4.dist-info}/METADATA +1 -1
- {datashare_python-0.11.2.dist-info → datashare_python-0.11.4.dist-info}/RECORD +6 -6
- {datashare_python-0.11.2.dist-info → datashare_python-0.11.4.dist-info}/WHEEL +1 -1
- {datashare_python-0.11.2.dist-info → datashare_python-0.11.4.dist-info}/entry_points.txt +0 -0
datashare_python/utils.py
CHANGED
|
@@ -10,6 +10,7 @@ import sys
|
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
12
|
from collections.abc import (
|
|
13
|
+
AsyncGenerator,
|
|
13
14
|
AsyncIterable,
|
|
14
15
|
Awaitable,
|
|
15
16
|
Callable,
|
|
@@ -522,7 +523,7 @@ def _write_artifact_bytes(path: Path, artifact: bytes | BytesIO | Path) -> None:
|
|
|
522
523
|
def _remove_previous_artifacts(path: Path) -> None:
|
|
523
524
|
if isinstance(path, Path) and path.exists():
|
|
524
525
|
with contextlib.suppress(FileNotFoundError):
|
|
525
|
-
|
|
526
|
+
shutil.rmtree(path)
|
|
526
527
|
else:
|
|
527
528
|
path.unlink(missing_ok=True)
|
|
528
529
|
|
|
@@ -766,59 +767,108 @@ class SharedResources:
|
|
|
766
767
|
eviction_callback: Callable[[Any, Any], None] | None = None,
|
|
767
768
|
) -> None:
|
|
768
769
|
self._eviction_callback = eviction_callback
|
|
769
|
-
self._cache = LRU(cache_size, self.
|
|
770
|
+
self._cache = LRU(cache_size, self._on_evict)
|
|
770
771
|
self._sentinel = object()
|
|
772
|
+
# Leased resources are tracked by id, evicting a leased resource defers the
|
|
773
|
+
# eviction callback until its last lease is released
|
|
774
|
+
self._lock = threading.Lock()
|
|
775
|
+
self._n_leases: dict[int, int] = dict()
|
|
776
|
+
self._pending_evictions: dict[int, tuple[Any, Any]] = dict()
|
|
771
777
|
|
|
772
778
|
def __enter__(self) -> Self:
|
|
773
779
|
return self
|
|
774
780
|
|
|
775
781
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
self._eviction_callback(k, v)
|
|
782
|
+
for k, v in self._cache.items():
|
|
783
|
+
self._on_evict(k, v)
|
|
779
784
|
|
|
785
|
+
@contextlib.contextmanager
|
|
780
786
|
def get_or_cache_resource(
|
|
781
787
|
self, key: str, default_factory: Callable[[], Any]
|
|
782
|
-
) -> Any:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
788
|
+
) -> Generator[Any, None, None]:
|
|
789
|
+
"""Get or cache a resource, leasing it for the duration of the context.
|
|
790
|
+
|
|
791
|
+
A resource evicted while leased stays usable, its eviction callback only runs
|
|
792
|
+
once all its leases have been released.
|
|
793
|
+
|
|
794
|
+
:param key: resource cache key
|
|
795
|
+
:param default_factory: resource factory, called on cache miss
|
|
796
|
+
:return: the cached resource
|
|
797
|
+
"""
|
|
798
|
+
value = self._get_and_acquire(key)
|
|
799
|
+
if value is self._sentinel:
|
|
800
|
+
value = default_factory()
|
|
801
|
+
self._cache_and_acquire(key, value)
|
|
802
|
+
try:
|
|
803
|
+
yield value
|
|
804
|
+
finally:
|
|
805
|
+
self._release(value)
|
|
800
806
|
|
|
807
|
+
@contextlib.asynccontextmanager
|
|
801
808
|
async def async_get_or_cache_resource(
|
|
802
809
|
self, key: str, default_factory: Callable[[], Awaitable[Any]]
|
|
803
|
-
) -> Any:
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
810
|
+
) -> AsyncGenerator[Any, None]:
|
|
811
|
+
"""Async version of get_or_cache_resource.
|
|
812
|
+
|
|
813
|
+
:param key: resource cache key
|
|
814
|
+
:param default_factory: async resource factory, awaited on cache miss
|
|
815
|
+
:return: the cached resource
|
|
816
|
+
"""
|
|
817
|
+
value = self._get_and_acquire(key)
|
|
818
|
+
if value is self._sentinel:
|
|
819
|
+
value = await default_factory()
|
|
820
|
+
self._cache_and_acquire(key, value)
|
|
821
|
+
try:
|
|
822
|
+
yield value
|
|
823
|
+
finally:
|
|
824
|
+
self._release(value)
|
|
825
|
+
|
|
826
|
+
def _get_and_acquire(self, key: str) -> Any:
|
|
827
|
+
with self._lock:
|
|
828
|
+
value = self._cache.get(key, self._sentinel)
|
|
829
|
+
if value is not self._sentinel:
|
|
830
|
+
self._acquire(value)
|
|
820
831
|
return value
|
|
821
832
|
|
|
833
|
+
def _cache_and_acquire(self, key: str, value: Any) -> None:
|
|
834
|
+
# The factory can be long to complete (that's one of the reason we want to
|
|
835
|
+
# cache its results). Because we don't lock the factory call, together with
|
|
836
|
+
# the cache update, in case of concurrent access, quicker factories will
|
|
837
|
+
# complete first and will stay longer in the cache. This is fine, the
|
|
838
|
+
# alternative is to lock factory + cache update to avoid concurrent access but
|
|
839
|
+
# this will mean waiting for the first factory call to complete, this can
|
|
840
|
+
# potentially imply longer waits than no getting the value from the cache
|
|
841
|
+
with self._lock:
|
|
842
|
+
self._acquire(value)
|
|
843
|
+
# Can evict other resources, must be called without holding the lock
|
|
844
|
+
self._cache[key] = value
|
|
845
|
+
|
|
846
|
+
def _acquire(self, value: Any) -> None:
|
|
847
|
+
value_id = id(value)
|
|
848
|
+
self._n_leases[value_id] = self._n_leases.get(value_id, 0) + 1
|
|
849
|
+
|
|
850
|
+
def _release(self, value: Any) -> None:
|
|
851
|
+
value_id = id(value)
|
|
852
|
+
with self._lock:
|
|
853
|
+
self._n_leases[value_id] -= 1
|
|
854
|
+
if self._n_leases[value_id]:
|
|
855
|
+
return
|
|
856
|
+
del self._n_leases[value_id]
|
|
857
|
+
pending = self._pending_evictions.pop(value_id, None)
|
|
858
|
+
if pending is not None:
|
|
859
|
+
self._evict(*pending)
|
|
860
|
+
|
|
861
|
+
def _on_evict(self, key: Any, value: Any) -> None:
|
|
862
|
+
with self._lock:
|
|
863
|
+
if id(value) in self._n_leases:
|
|
864
|
+
self._pending_evictions[id(value)] = (key, value)
|
|
865
|
+
return
|
|
866
|
+
self._evict(key, value)
|
|
867
|
+
|
|
868
|
+
def _evict(self, key: Any, value: Any) -> None:
|
|
869
|
+
if self._eviction_callback is not None:
|
|
870
|
+
self._eviction_callback(key, value)
|
|
871
|
+
|
|
822
872
|
|
|
823
873
|
def close_cm_callback(key: str, value: Any) -> None: # noqa: ARG001
|
|
824
874
|
if hasattr(value, "__exit__"):
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datashare-python
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.4
|
|
4
4
|
Summary: Manage Python tasks and local resources in Datashare
|
|
5
5
|
Project-URL: Homepage, https://icij.github.io/datashare-python/
|
|
6
6
|
Project-URL: Documentation, https://icij.github.io/datashare-python/
|
|
@@ -14,15 +14,15 @@ datashare_python/objects.py,sha256=RuDEC4dZPndMYwFKhmMlToY8jjwjupG7mGGCG5tzc6g,1
|
|
|
14
14
|
datashare_python/task_client.py,sha256=oTmP8bvZW0UyhLNMi1AV3XIAx7hrdbxNRss2Mw2azEc,8435
|
|
15
15
|
datashare_python/template.py,sha256=N1uM8TRXh5tQPV7BC1oJj-Pdm2Gs_SkjaXv50RnNYso,4682
|
|
16
16
|
datashare_python/types_.py,sha256=cPcCkIk6ecILrKetPnAsrb0njHYuWJU3eZicUXy2lF8,1278
|
|
17
|
-
datashare_python/utils.py,sha256=
|
|
18
|
-
datashare_python/worker-template.tar.gz,sha256=
|
|
17
|
+
datashare_python/utils.py,sha256=MU_laix9TclXQ7Pt0mqqYQylYR3OKU-MItjQupcG3c8,30183
|
|
18
|
+
datashare_python/worker-template.tar.gz,sha256=5U1vAMg7xtJSKAz20kqz7QLknvzmREN5ddqK7RvAYyI,236093
|
|
19
19
|
datashare_python/worker.py,sha256=ad36_yJOgwtj8KBheWndGkqaP_2aXTPSrYaHrbCXTvg,8389
|
|
20
20
|
datashare_python/cli/__init__.py,sha256=9BPWtssDgsVfWMsZ1TtZCla0EC_kai4RHttr8oNLYOE,1401
|
|
21
21
|
datashare_python/cli/project.py,sha256=w32Gy9AOL5B00uDT4in7YUCt2g68FnNbvwg2M3a8G6o,946
|
|
22
22
|
datashare_python/cli/task.py,sha256=GsyR5o0fN4Pd8tJKAUgivAvCBKT3L4Ay5j_BCW1XBHk,5828
|
|
23
23
|
datashare_python/cli/utils.py,sha256=p69CQb0zfixuyBkiZprhdMCc_NuYwXyAn6vC9H1UzAw,911
|
|
24
24
|
datashare_python/cli/worker.py,sha256=x0p3a1_NsZPbmV7-WSVMRyW3_iadYcgyZKbDvWgymQc,5790
|
|
25
|
-
datashare_python-0.11.
|
|
26
|
-
datashare_python-0.11.
|
|
27
|
-
datashare_python-0.11.
|
|
28
|
-
datashare_python-0.11.
|
|
25
|
+
datashare_python-0.11.4.dist-info/METADATA,sha256=wesn34A1Y6Omeo3gftg-Jd4jiXo4a4hktN1blM3vCJI,1012
|
|
26
|
+
datashare_python-0.11.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
27
|
+
datashare_python-0.11.4.dist-info/entry_points.txt,sha256=ILE7auxabHWiu3GC-AunWnzjhOI_SbZp7D4GqZHlLw4,68
|
|
28
|
+
datashare_python-0.11.4.dist-info/RECORD,,
|
|
File without changes
|