wool 0.1rc8__py3-none-any.whl → 0.1rc10__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 wool might be problematic. Click here for more details.

Files changed (43) hide show
  1. wool/__init__.py +71 -50
  2. wool/_protobuf/__init__.py +14 -0
  3. wool/_protobuf/exception.py +3 -0
  4. wool/_protobuf/task.py +11 -0
  5. wool/_protobuf/task_pb2.py +42 -0
  6. wool/_protobuf/task_pb2.pyi +43 -0
  7. wool/_protobuf/{mempool/metadata/metadata_pb2_grpc.py → task_pb2_grpc.py} +2 -2
  8. wool/_protobuf/worker.py +24 -0
  9. wool/_protobuf/worker_pb2.py +47 -0
  10. wool/_protobuf/worker_pb2.pyi +39 -0
  11. wool/_protobuf/worker_pb2_grpc.py +141 -0
  12. wool/_resource_pool.py +376 -0
  13. wool/_typing.py +0 -10
  14. wool/_work.py +553 -0
  15. wool/_worker.py +843 -169
  16. wool/_worker_discovery.py +1223 -0
  17. wool/_worker_pool.py +331 -0
  18. wool/_worker_proxy.py +515 -0
  19. {wool-0.1rc8.dist-info → wool-0.1rc10.dist-info}/METADATA +8 -7
  20. wool-0.1rc10.dist-info/RECORD +22 -0
  21. wool-0.1rc10.dist-info/entry_points.txt +2 -0
  22. wool/_cli.py +0 -262
  23. wool/_event.py +0 -109
  24. wool/_future.py +0 -171
  25. wool/_logging.py +0 -44
  26. wool/_manager.py +0 -181
  27. wool/_mempool/__init__.py +0 -4
  28. wool/_mempool/_mempool.py +0 -311
  29. wool/_mempool/_metadata.py +0 -39
  30. wool/_mempool/_service.py +0 -225
  31. wool/_pool.py +0 -524
  32. wool/_protobuf/mempool/mempool_pb2.py +0 -66
  33. wool/_protobuf/mempool/mempool_pb2.pyi +0 -108
  34. wool/_protobuf/mempool/mempool_pb2_grpc.py +0 -312
  35. wool/_protobuf/mempool/metadata/metadata_pb2.py +0 -36
  36. wool/_protobuf/mempool/metadata/metadata_pb2.pyi +0 -17
  37. wool/_queue.py +0 -32
  38. wool/_session.py +0 -429
  39. wool/_task.py +0 -366
  40. wool/_utils.py +0 -63
  41. wool-0.1rc8.dist-info/RECORD +0 -28
  42. wool-0.1rc8.dist-info/entry_points.txt +0 -2
  43. {wool-0.1rc8.dist-info → wool-0.1rc10.dist-info}/WHEEL +0 -0
wool/_manager.py DELETED
@@ -1,181 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from functools import partial
5
- from multiprocessing.managers import BaseManager
6
- from multiprocessing.managers import DictProxy
7
- from queue import Empty
8
- from threading import Event
9
- from threading import Lock
10
- from typing import TYPE_CHECKING
11
- from typing import Any
12
- from typing import Callable
13
- from typing import TypeVar
14
- from typing import overload
15
- from uuid import UUID
16
- from weakref import WeakValueDictionary
17
-
18
- from wool._event import TaskEvent
19
- from wool._future import Future
20
- from wool._queue import TaskQueue
21
- from wool._typing import PassthroughDecorator
22
-
23
- if TYPE_CHECKING:
24
- from wool._task import Task
25
-
26
-
27
- C = TypeVar("C", bound=Callable[..., Any])
28
-
29
- _manager_registry = {}
30
-
31
-
32
- def _register(fn, /, **kwargs):
33
- assert fn.__name__ not in _manager_registry, (
34
- f"Function '{fn.__name__}' already registered"
35
- )
36
- _manager_registry[fn.__name__] = (fn, kwargs)
37
- return fn
38
-
39
-
40
- @overload
41
- def register(
42
- fn: C,
43
- /,
44
- *,
45
- exposed: tuple[str, ...] | None = None,
46
- proxytype: None = None,
47
- method_to_typeid: None = None,
48
- ) -> C: ...
49
-
50
-
51
- @overload
52
- def register(
53
- fn: None = None,
54
- /,
55
- *,
56
- exposed: tuple[str, ...] | None = None,
57
- proxytype: type | None = None,
58
- method_to_typeid: dict[str, str] | None = None,
59
- ) -> PassthroughDecorator[C]: ...
60
-
61
-
62
- def register(
63
- fn: C | None = None,
64
- /,
65
- *,
66
- exposed: tuple[str, ...] | None = None,
67
- proxytype: type | None = None,
68
- method_to_typeid: dict[str, str] | None = None,
69
- ) -> PassthroughDecorator[C] | C:
70
- kwargs = {}
71
- if exposed is not None:
72
- kwargs["exposed"] = exposed
73
- if proxytype is not None:
74
- kwargs["proxytype"] = proxytype
75
- if method_to_typeid is not None:
76
- kwargs["method_to_typeid"] = method_to_typeid
77
- if fn:
78
- return _register(fn, **kwargs)
79
- else:
80
- return partial(_register, **kwargs)
81
-
82
-
83
- class FuturesProxy(DictProxy):
84
- assert (method_to_typeid := getattr(DictProxy, "_method_to_typeid_"))
85
- _method_to_typeid_ = {
86
- **method_to_typeid,
87
- "__getitem__": "proxify",
88
- "setdefault": "proxify",
89
- }
90
-
91
-
92
- _task_queue: TaskQueue[Task] = TaskQueue(100000, None)
93
-
94
- _task_queue_lock = Lock()
95
-
96
- _task_futures: WeakValueDictionary[UUID, Future] = WeakValueDictionary()
97
-
98
-
99
- @register
100
- def put(task: Task) -> Future:
101
- try:
102
- with queue_lock():
103
- queue().put(task, block=False)
104
- future = futures()[task.id] = Future()
105
- TaskEvent("task-queued", task=task).emit()
106
- return future
107
- except Exception as e:
108
- logging.exception(e)
109
- raise
110
-
111
-
112
- @register
113
- def get() -> Task | Empty | None:
114
- try:
115
- return queue().get(block=False)
116
- except Empty as e:
117
- return e
118
-
119
-
120
- @register
121
- def proxify(value):
122
- return value
123
-
124
-
125
- @register(proxytype=FuturesProxy)
126
- def futures():
127
- global _task_futures
128
- if not _task_futures:
129
- _task_futures = WeakValueDictionary()
130
- return _task_futures
131
-
132
-
133
- @register
134
- def queue() -> TaskQueue:
135
- global _task_queue
136
- if not _task_queue:
137
- _task_queue = TaskQueue(1000, None)
138
- return _task_queue
139
-
140
-
141
- @register
142
- def queue_lock() -> Lock:
143
- global _task_queue_lock
144
- if not _task_queue_lock:
145
- _task_queue_lock = Lock()
146
- return _task_queue_lock
147
-
148
-
149
- _stop_event = Event()
150
- _wait_event = Event()
151
-
152
-
153
- @register(exposed=("is_set", "set", "clear", "wait"))
154
- def stopping() -> Event:
155
- return _stop_event
156
-
157
-
158
- @register(exposed=("is_set", "set", "clear", "wait"))
159
- def waiting() -> Event:
160
- return _wait_event
161
-
162
-
163
- class ManagerMeta(type):
164
- def __new__(mcs, name, bases, attrs):
165
- cls = super().__new__(mcs, name, bases, attrs)
166
- assert (register := getattr(cls, "register"))
167
- for name, (fn, kwargs) in _manager_registry.items():
168
- register(name, callable=fn, **kwargs)
169
- return cls
170
-
171
-
172
- class Manager(BaseManager, metaclass=ManagerMeta):
173
- if TYPE_CHECKING:
174
- put = staticmethod(put)
175
- get = staticmethod(get)
176
- proxify = staticmethod(proxify)
177
- futures = staticmethod(futures)
178
- queue = staticmethod(queue)
179
- queue_lock = staticmethod(queue_lock)
180
- stopping = staticmethod(stopping)
181
- waiting = staticmethod(waiting)
wool/_mempool/__init__.py DELETED
@@ -1,4 +0,0 @@
1
- from wool._mempool._mempool import MemoryPool
2
- from wool._mempool._service import MemoryPoolService
3
-
4
- __all__ = ["MemoryPool", "MemoryPoolService"]
wool/_mempool/_mempool.py DELETED
@@ -1,311 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import hashlib
5
- import mmap
6
- import os
7
- import pathlib
8
- import shutil
9
- from contextlib import asynccontextmanager
10
- from typing import BinaryIO
11
-
12
- try:
13
- from typing import Self
14
- except ImportError:
15
- from typing_extensions import Self
16
-
17
- import shortuuid
18
-
19
- from wool._mempool._metadata import MetadataMessage
20
-
21
-
22
- class SharedObject:
23
- _id: str
24
- _mempool: MemoryPool
25
- _file: BinaryIO
26
- _mmap: mmap.mmap
27
- _size: int
28
- _md5: bytes
29
-
30
- def __init__(self, id: str, *, mempool: MemoryPool):
31
- self._id = id
32
- self._mempool = mempool
33
- self._file = open(self._path / "dump", "r+b")
34
- self._mmap = mmap.mmap(self._file.fileno(), 0)
35
- self._size = self.metadata.size
36
- self._md5 = self.metadata.md5
37
-
38
- def __del__(self):
39
- try:
40
- self.close()
41
- except Exception:
42
- pass
43
-
44
- @property
45
- def id(self) -> str:
46
- return self._id
47
-
48
- @property
49
- def metadata(self) -> SharedObjectMetadata:
50
- return SharedObjectMetadata(self.id, mempool=self._mempool)
51
-
52
- @property
53
- def mmap(self) -> mmap.mmap:
54
- return self._mmap
55
-
56
- @property
57
- def _path(self) -> pathlib.Path:
58
- return pathlib.Path(self._mempool.path, self.id)
59
-
60
- def close(self):
61
- self.metadata.close()
62
- self._mmap.close()
63
- self._file.close()
64
-
65
- def refresh(self) -> Self:
66
- if self._size != self.metadata.size or self._md5 != self.metadata.md5:
67
- self._mmap.close()
68
- self._file.close()
69
- self._file = open(self._path / "dump", "r+b")
70
- self._mmap = mmap.mmap(self._file.fileno(), 0)
71
- self._size = self.metadata.size
72
- self._md5 = self.metadata.md5
73
- return self
74
-
75
-
76
- class SharedObjectMetadata:
77
- _id: str
78
- _mempool: MemoryPool
79
- _file: BinaryIO
80
- _mmap: mmap.mmap
81
- _instances: dict[str, SharedObjectMetadata] = {}
82
-
83
- def __new__(cls, id: str, *, mempool: MemoryPool):
84
- if id in cls._instances:
85
- return cls._instances[id]
86
- return super().__new__(cls)
87
-
88
- def __init__(self, id: str, mempool: MemoryPool):
89
- self._id = id
90
- self._mempool = mempool
91
- self._file = open(self._path / "meta", "r+b")
92
- self._mmap = mmap.mmap(self._file.fileno(), 0)
93
- self._instances[id] = self
94
-
95
- def __del__(self):
96
- try:
97
- self.close()
98
- except Exception:
99
- pass
100
-
101
- @property
102
- def id(self) -> str:
103
- return self._id
104
-
105
- @property
106
- def mutable(self) -> bool:
107
- return self._metadata.mutable
108
-
109
- @property
110
- def size(self) -> int:
111
- return self._metadata.size
112
-
113
- @property
114
- def md5(self) -> bytes:
115
- return self._metadata.md5
116
-
117
- @property
118
- def mmap(self) -> mmap.mmap:
119
- return self._mmap
120
-
121
- @property
122
- def _path(self) -> pathlib.Path:
123
- return pathlib.Path(self._mempool.path, self.id)
124
-
125
- @property
126
- def _metadata(self) -> MetadataMessage:
127
- return MetadataMessage.loads(bytes(self._mmap))
128
-
129
- def close(self):
130
- self._mmap.close()
131
- self._file.close()
132
- del self._instances[self.id]
133
-
134
-
135
- class MemoryPool:
136
- _objects: dict[str, SharedObject]
137
- _path: pathlib.Path
138
-
139
- def __init__(self, path: str | pathlib.Path = pathlib.Path(".mempool")):
140
- if isinstance(path, str):
141
- self._path = pathlib.Path(path)
142
- else:
143
- self._path = path
144
- self._lockdir = self._path / "locks"
145
- os.makedirs(self._lockdir, exist_ok=True)
146
- self._acquire(f"pid-{os.getpid()}")
147
- self._objects = dict()
148
-
149
- def __contains__(self, ref: str) -> bool:
150
- return ref in self._objects
151
-
152
- def __del__(self):
153
- self._release(f"pid-{os.getpid()}")
154
-
155
- @property
156
- def path(self) -> pathlib.Path:
157
- return self._path
158
-
159
- async def map(self, ref: str | None = None):
160
- if ref is not None and ref not in self._objects:
161
- if self._locked(f"delete-{ref}"):
162
- raise RuntimeError(
163
- f"Reference {ref} is currently locked for deletion"
164
- )
165
- async with self._reference_lock(ref):
166
- self._map(ref)
167
- else:
168
- for entry in os.scandir(self._path):
169
- if entry.is_dir() and (ref := entry.name) != "locks":
170
- if not self._locked(f"delete-{ref}"):
171
- async with self._reference_lock(ref):
172
- self._map(ref)
173
-
174
- async def put(
175
- self, dump: bytes, *, mutable: bool = False, ref: str | None = None
176
- ) -> str:
177
- ref = ref or str(shortuuid.uuid())
178
- async with self._reference_lock(ref):
179
- self._put(ref, dump, mutable=mutable, exist_ok=False)
180
- return ref
181
-
182
- async def post(self, ref: str, dump: bytes) -> bool:
183
- if self._locked(f"delete-{ref}"):
184
- raise RuntimeError(
185
- f"Reference {ref} is currently locked for deletion"
186
- )
187
- async with self._reference_lock(ref):
188
- if ref not in self._objects:
189
- self._map(ref)
190
- obj = self._objects[ref]
191
- if not obj.metadata.mutable:
192
- raise ValueError("Cannot modify an immutable reference")
193
- if (size := len(dump)) != obj.metadata.size:
194
- try:
195
- obj.mmap.resize(size)
196
- self._post(ref, obj, dump)
197
- except SystemError:
198
- self._put(ref, dump, mutable=True, exist_ok=True)
199
- return True
200
- elif hashlib.md5(dump).digest() != obj.metadata.md5:
201
- self._post(ref, obj, dump)
202
- return True
203
- else:
204
- return False
205
-
206
- async def get(self, ref: str) -> bytes:
207
- if self._locked(f"delete-{ref}"):
208
- raise RuntimeError(
209
- f"Reference {ref} is currently locked for deletion"
210
- )
211
- async with self._reference_lock(ref):
212
- if ref not in self._objects:
213
- self._map(ref)
214
- return bytes(self._objects[ref].refresh().mmap)
215
-
216
- async def delete(self, ref: str):
217
- async with self._delete_lock(ref):
218
- async with self._reference_lock(ref):
219
- if ref not in self._objects:
220
- self._map(ref)
221
- self._objects.pop(ref).close()
222
- try:
223
- shutil.rmtree(self.path / ref)
224
- except FileNotFoundError:
225
- pass
226
-
227
- def _put(
228
- self,
229
- ref: str,
230
- dump: bytes,
231
- *,
232
- mutable: bool = False,
233
- exist_ok: bool = False,
234
- ):
235
- metadata = MetadataMessage(
236
- ref=ref,
237
- mutable=mutable,
238
- size=len(dump),
239
- md5=hashlib.md5(dump).digest(),
240
- )
241
-
242
- refpath = pathlib.Path(self._path, f"{ref}")
243
- os.makedirs(refpath, exist_ok=exist_ok)
244
-
245
- with open(refpath / "meta", "wb") as metafile:
246
- metafile.write(metadata.dumps())
247
-
248
- with open(refpath / "dump", "wb") as dumpfile:
249
- dumpfile.write(dump)
250
-
251
- self._map(ref)
252
-
253
- def _post(self, ref: str, obj: SharedObject, dump: bytes):
254
- if not obj.metadata.mutable:
255
- raise ValueError("Cannot modify an immutable reference")
256
- metadata = MetadataMessage(
257
- ref=ref,
258
- mutable=True,
259
- size=len(dump),
260
- md5=hashlib.md5(dump).digest(),
261
- )
262
- obj.metadata.mmap[:] = metadata.dumps()
263
- obj.metadata.mmap.flush()
264
- obj.mmap.seek(0)
265
- obj.mmap.write(dump)
266
-
267
- def _map(self, ref: str):
268
- obj = self._objects.pop(ref, None)
269
- if obj:
270
- obj.close()
271
- self._objects[ref] = SharedObject(id=ref, mempool=self)
272
-
273
- def _lockpath(self, key: str) -> pathlib.Path:
274
- return pathlib.Path(self._lockdir, f"{key}.lock")
275
-
276
- def _acquire(self, key: str) -> bool:
277
- try:
278
- os.symlink(f"{key}", self._lockpath(key))
279
- return True
280
- except FileExistsError:
281
- return False
282
-
283
- def _release(self, key: str):
284
- try:
285
- if os.path.islink(lock_path := self._lockpath(key)):
286
- os.unlink(lock_path)
287
- except FileNotFoundError:
288
- pass
289
-
290
- def _locked(self, key: str) -> bool:
291
- return os.path.islink(self._lockpath(key))
292
-
293
- @asynccontextmanager
294
- async def _reference_lock(self, ref: str):
295
- try:
296
- while not self._acquire(ref):
297
- await asyncio.sleep(0)
298
- yield
299
- finally:
300
- self._release(ref)
301
-
302
- @asynccontextmanager
303
- async def _delete_lock(self, ref: str):
304
- key = f"delete-{ref}"
305
- if not self._acquire(f"delete-{ref}"):
306
- raise RuntimeError(
307
- f"Reference {ref} is currently locked for deletion"
308
- )
309
- else:
310
- yield
311
- self._release(key)
@@ -1,39 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from dataclasses import dataclass
5
-
6
- try:
7
- from wool._protobuf.mempool.metadata.metadata_pb2 import _MetadataMessage
8
- except ImportError:
9
- logging.error(
10
- "Failed to import _MetadataMessage. "
11
- "Ensure protocol buffers are compiled."
12
- )
13
- raise
14
-
15
-
16
- @dataclass
17
- class MetadataMessage:
18
- ref: str
19
- mutable: bool
20
- size: int
21
- md5: bytes
22
-
23
- @classmethod
24
- def loads(cls, data: bytes) -> MetadataMessage:
25
- (metadata := _MetadataMessage()).ParseFromString(data)
26
- return cls(
27
- ref=metadata.ref,
28
- mutable=metadata.mutable,
29
- size=metadata.size,
30
- md5=metadata.md5,
31
- )
32
-
33
- def dumps(self) -> bytes:
34
- return _MetadataMessage(
35
- ref=self.ref, mutable=self.mutable, size=self.size, md5=self.md5
36
- ).SerializeToString()
37
-
38
-
39
- __all__ = ["MetadataMessage"]