object-pool-async 0.1.2__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.
@@ -0,0 +1,10 @@
1
+ """
2
+ 对象池。
3
+ """
4
+
5
+ from .exceptions import PoolError, ObjectNotFoundError
6
+ from .status import Status
7
+ from .pool import (
8
+ Pool,
9
+ Object, ObjectWrapper, ObjectPack
10
+ )
@@ -0,0 +1,32 @@
1
+ from typing import Any
2
+
3
+
4
+ class AttributeDict(dict):
5
+ """
6
+ A class that can read / set attribute value via both `.` and `[]`.
7
+ """
8
+
9
+ def __getattr__(self, key: str) -> Any:
10
+ """
11
+ Get an attribute value by `.`
12
+
13
+ Args:
14
+ key (str): The name of the attribute to get.
15
+
16
+ Returns:
17
+ Any: The value of the attribute.
18
+ """
19
+ return self[key]
20
+
21
+ def __setattr__(self, key: str, value: Any) -> None:
22
+ """
23
+ Set an attribute value by `.`
24
+
25
+ Args:
26
+ key (str): The name of the attribute to set.
27
+ value (Any): The value to set the attribute to.
28
+ """
29
+ self[key] = value
30
+
31
+ def __repr__(self) -> str:
32
+ return f"{self.__class__.__name__}({super().__repr__()})"
@@ -0,0 +1,14 @@
1
+ """
2
+ 异常
3
+ """
4
+
5
+ class PoolError(Exception):
6
+ """
7
+ 与对象池有关的错误。
8
+ """
9
+
10
+
11
+ class ObjectNotFoundError(PoolError):
12
+ """
13
+ 在池中没有找到对象。
14
+ """
object_pool/pool.py ADDED
@@ -0,0 +1,441 @@
1
+ """
2
+ class: Pool
3
+ """
4
+
5
+ from __future__ import annotations
6
+ import asyncio
7
+ from collections.abc import Callable, Coroutine
8
+ from datetime import datetime, timedelta
9
+ import functools
10
+ from numbers import Number
11
+ import random
12
+ from typing import Any, Dict, Generic, List, Optional, Sequence, TypeVar
13
+
14
+ from .collections import AttributeDict
15
+ from .exceptions import ObjectNotFoundError
16
+ from .status import Status
17
+ from .utils import AsyncRLock, to_async
18
+
19
+
20
+ # 池中对象
21
+ Object = TypeVar("Object")
22
+
23
+
24
+ # 包装器
25
+ class ObjectWrapper(AttributeDict, Generic[Object]):
26
+ """
27
+ 对象包装器,用于储存对象的数据。
28
+ """
29
+ def __init__(self, object: Object, status: Status = Status.PENDING, **kwargs):
30
+ super(AttributeDict, self).__init__(**kwargs)
31
+ self.object: Object = object
32
+ self.status: Status = status
33
+
34
+ def __str__(self) -> str:
35
+ return f"{type(self).__name__}<{self.object}: {self.status}>"
36
+
37
+
38
+ # 打包器
39
+ class ObjectPack(Generic[Object]):
40
+ """
41
+ 对象包裹,当对象被借出时返回。
42
+ """
43
+ def __init__(self, object_wrapper: ObjectWrapper[Object]):
44
+ self._object_wrapper = object_wrapper
45
+
46
+ def __str__(self) -> str:
47
+ return f"{type(self).__name__}<{self.object}>"
48
+
49
+
50
+ @property
51
+ def object(self):
52
+ """
53
+ 返回对象。
54
+ """
55
+ return self._object_wrapper.object
56
+
57
+
58
+ def close(self, **feedback) -> None:
59
+ """
60
+ 归还借出的对象。
61
+ """
62
+ self._object_wrapper.status = Status.PENDING
63
+ self._object_wrapper.update(feedback)
64
+
65
+
66
+ def __enter__(self) -> Object:
67
+ return self.object
68
+
69
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
70
+ self.close()
71
+ self._object_wrapper = None
72
+
73
+ async def __aenter__(self) -> Object:
74
+ return self.__enter__()
75
+
76
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
77
+ return self.__exit__(exc_type, exc_val, exc_tb)
78
+
79
+
80
+ def _split_chunk(objects: List[Any], chunk_size: Optional[int] = None) -> List[List[Any]]:
81
+ """
82
+ 将对象列表按每组 chunk_size 个,分组。
83
+ """
84
+ if not chunk_size or chunk_size < 0:
85
+ return [objects]
86
+
87
+ return [
88
+ objects[i : i + chunk_size]
89
+ for i in range(0, len(objects), chunk_size)
90
+ ]
91
+
92
+
93
+ def _choice(seq: Sequence[Any]) -> Optional[Any]:
94
+ """
95
+ 默认的随机选择函数。
96
+ """
97
+ if not seq:
98
+ return None
99
+ return random.choice(seq)
100
+
101
+
102
+ def async_locked(method: Callable) -> Callable:
103
+ """装饰器:为异步方法自动加上 RLock"""
104
+ @functools.wraps(method)
105
+ async def wrapper(self, *args, **kwargs):
106
+ async with self._lock_async:
107
+ return await method(self, *args, **kwargs)
108
+ return wrapper
109
+
110
+
111
+ class Pool(Generic[Object]):
112
+ """
113
+ 对象池。
114
+ """
115
+ # 对象的ID
116
+ ID = int
117
+
118
+ # 对象状态检测器
119
+ StatusDetector = Callable[
120
+ [ObjectWrapper[Object]],
121
+ Status
122
+ ]
123
+ StatusDetectorAsync = Callable[
124
+ [ObjectWrapper[Object]],
125
+ Coroutine[None, None, Status]
126
+ ]
127
+
128
+ # 对象选择器
129
+ ObjectSelector = Callable[
130
+ [List[ObjectWrapper[Object]], ...],
131
+ Optional[ObjectWrapper[Object]]
132
+ ]
133
+ ObjectSelectorAsync = Callable[
134
+ [List[ObjectWrapper[Object]], ...],
135
+ Coroutine[None, None, Optional[ObjectWrapper[Object]]]
136
+ ]
137
+
138
+ # 包装工厂函数
139
+ Wrapper = Callable[
140
+ [Object],
141
+ ObjectWrapper[Object]
142
+ ]
143
+ WrapperAsync = Callable[
144
+ [Object],
145
+ Coroutine[None, None, ObjectWrapper[Object]]
146
+ ]
147
+
148
+ # 打包工厂函数
149
+ Packer = Callable[
150
+ [ObjectWrapper[Object]],
151
+ ObjectPack[Object]
152
+ ]
153
+ PackerAsync = Callable[
154
+ [ObjectWrapper[Object]],
155
+ Coroutine[None, None, ObjectPack[Object]]
156
+ ]
157
+
158
+ def __init__(
159
+ self,
160
+ detect_status: StatusDetector | StatusDetectorAsync,
161
+ select_object: ObjectSelector | ObjectSelectorAsync = _choice,
162
+ *,
163
+ wrapper: Wrapper | WrapperAsync = ObjectWrapper,
164
+ packer: Packer | PackerAsync = ObjectPack,
165
+ wrapping_data: Optional[Dict[str, Any]] = None,
166
+ async_concurrency: Optional[int] = None
167
+ ):
168
+ """
169
+ 初始化一个 Pool 对象。
170
+
171
+ Args:
172
+ detect_status (StatusDetector | StatusDetectorAsync): 检测对象状态的函数,内部会把它变成异步函数。
173
+ select_object (ObjectSelector | ObjectSelectorAsync): 从容器中选择一个对象。默认随机选择。
174
+ wrapper (Callable[[Object], ObjectWrapper]): 包装对象的可调用对象。
175
+ packer (Callable[[ObjectWrapper], ObjectPack]): 打包对象的可调用对象。
176
+ wrapping_data (Dict[str, Any]): 添加对象时,默认的数据。
177
+ async_concurrency (int): 最大异步并发数。若缺省,则全部并发。
178
+ """
179
+ self._object_wrappers : Dict[self.ID, ObjectWrapper[Object]] = {}
180
+ self._detect_status_async : self.StatusDetectorAsync = to_async(detect_status)
181
+ self._select_object_async : self.ObjectSelectorAsync = to_async(select_object)
182
+ self._wrapper_async : self.WrapperAsync = to_async(wrapper)
183
+ self._packer_async : self.PackerAsync = to_async(packer)
184
+ self._default_wrapping_data : Dict[str, Any] = wrapping_data or {}
185
+ self._async_concurrency : int = async_concurrency
186
+ self._due_datetime : Dict[self.ID, datetime] = {}
187
+ self._lock_async = AsyncRLock()
188
+
189
+
190
+ def __len__(self) -> int:
191
+ return len(self._object_wrappers)
192
+
193
+
194
+ def __contains__(self, obj: Object) -> bool:
195
+ _id = id(obj)
196
+ return _id in self._object_wrappers
197
+
198
+
199
+ def _split_chunk(self, objects: List[Any]) -> List[List[Any]]:
200
+ """
201
+ 将对象列表按每组 self._async_concurrency 个,分组。
202
+ """
203
+ return _split_chunk(objects, self._async_concurrency)
204
+
205
+
206
+ async def _get_wrapper_async(self, obj: Object) -> ObjectWrapper[Object]:
207
+ """
208
+ 返回 `obj` 的包装器对象。
209
+
210
+ Raises:
211
+ ObjectNotFoundError: 如果 obj 不在池中。
212
+ """
213
+ _id = id(obj)
214
+ if _id not in self._object_wrappers:
215
+ raise ObjectNotFoundError(str(obj))
216
+ return self._object_wrappers[_id]
217
+
218
+
219
+ @async_locked
220
+ async def add_async(self, obj: Object, **kwargs) -> ObjectWrapper[Object]:
221
+ """
222
+ 向池中添加/覆盖一个对象。
223
+ """
224
+ kwargs = self._default_wrapping_data | kwargs
225
+ _id = id(obj)
226
+ self._object_wrappers[_id] = await self._wrapper_async(obj, **kwargs)
227
+ return await self._get_wrapper_async(obj)
228
+
229
+
230
+ async def _delete_async(self, obj: Object) -> None:
231
+ """
232
+ 从池中丢弃一个对象。
233
+
234
+ Raises:
235
+ ObjectNotFoundError: 如果 obj 不在池中。
236
+ """
237
+ _id = id(obj)
238
+ if _id not in self._object_wrappers:
239
+ raise ObjectNotFoundError(str(obj))
240
+ self._object_wrappers.pop(_id)
241
+ self._due_datetime.pop(_id, None)
242
+
243
+
244
+ @async_locked
245
+ async def discard_async(self, obj: Object) -> None:
246
+ """
247
+ 从池中丢弃一个对象。
248
+
249
+ 如果池中没有该对象,不会报错。
250
+ """
251
+ if obj in self:
252
+ await self._delete_async(obj)
253
+
254
+
255
+ @async_locked
256
+ async def remove_async(self, obj: Object) -> None:
257
+ """
258
+ 从池中丢弃一个对象。
259
+
260
+ Raises:
261
+ ObjectNotFoundError: 如果 obj 不在池中。
262
+ """
263
+ await self._delete_async(obj)
264
+
265
+
266
+ @async_locked
267
+ async def clear_async(self) -> None:
268
+ """
269
+ 清空对象池。
270
+ """
271
+ self._object_wrappers.clear()
272
+ self._due_datetime.clear()
273
+
274
+
275
+ async def _get_status_by_id_async(self, _id: ID) -> Status:
276
+ """
277
+ 通过 id 查询对象状态。
278
+ """
279
+ if _id not in self._object_wrappers:
280
+ raise ObjectNotFoundError(f"Can't find object with id {_id!r}")
281
+ object_wrapper = self._object_wrappers[_id]
282
+ return object_wrapper.status
283
+
284
+
285
+ @async_locked
286
+ async def get_status_async(self, obj: Object) -> Status:
287
+ """
288
+ 查询对象的状态。
289
+ """
290
+ _id = id(obj)
291
+ return await self._get_status_by_id_async(_id)
292
+
293
+
294
+ async def _detect_async(self) -> None:
295
+ """
296
+ 对池中对象进行检测,更新 status。
297
+ """
298
+ # 排除被借出的对象
299
+ object_wrappers_to_be_detected = [
300
+ object_wrapper
301
+ for object_wrapper in self._object_wrappers.values()
302
+ if object_wrapper.status != Status.BORROWED
303
+ ]
304
+
305
+ # 异步并发检测对象
306
+ object_wrapper_chunks = self._split_chunk(object_wrappers_to_be_detected)
307
+ for object_wrappers in object_wrapper_chunks:
308
+ tasks = map(self._detect_status_async, object_wrappers)
309
+ statuses = await asyncio.gather(*tasks, return_exceptions=True)
310
+ for (object_wrapper, status) in zip(object_wrappers, statuses):
311
+ if isinstance(status, Exception):
312
+ # 记录日志,并将状态设为 UNKNOWN
313
+ object_wrapper.status = Status.UNKNOWN
314
+ else:
315
+ object_wrapper.status = status
316
+
317
+
318
+ async def _clean_async(self) -> None:
319
+ """
320
+ 删除池中不可用的对象。
321
+ """
322
+ # 挑出无效对象
323
+ objects_to_be_removed = [
324
+ object_wrapper.object
325
+ for object_wrapper in await self._get_object_wrappers_by_status_async(Status.INVALID)
326
+ ]
327
+
328
+ # 异步并发删除对象
329
+ object_chunks = self._split_chunk(objects_to_be_removed)
330
+ for objects in object_chunks:
331
+ tasks = map(self._delete_async, objects)
332
+ await asyncio.gather(*tasks, return_exceptions=True)
333
+
334
+
335
+ async def _withdraw_async(self) -> None:
336
+ """
337
+ 强制收回被借出的过期对象。
338
+ """
339
+ # 找出过期的
340
+ now = datetime.now()
341
+ withdrawn_ids = []
342
+ for (_id, due) in self._due_datetime.items():
343
+ if due < now:
344
+ withdrawn_ids.append(_id)
345
+
346
+ # 回收
347
+ for _id in withdrawn_ids:
348
+ self._due_datetime.pop(_id)
349
+ object_wrapper = self._object_wrappers.get(_id, None)
350
+ if object_wrapper and object_wrapper.status == Status.BORROWED:
351
+ object_wrapper.status = Status.PENDING
352
+
353
+
354
+ @async_locked
355
+ async def update_status_async(self) -> None:
356
+ """
357
+ 更新对象的状态记录。
358
+ """
359
+ await self._withdraw_async()
360
+ await self._detect_async()
361
+ await self._clean_async()
362
+
363
+
364
+ async def _get_object_wrappers_by_status_async(self, status: Status) -> List[ObjectWrapper[Object]]:
365
+ """
366
+ 根据状态查找对象。
367
+ """
368
+ return [
369
+ object_wrapper
370
+ for object_wrapper in self._object_wrappers.values()
371
+ if object_wrapper.status == status
372
+ ]
373
+
374
+
375
+ @async_locked
376
+ async def borrow_async(
377
+ self,
378
+ timeout: Optional[Number | timedelta] = None,
379
+ **kwargs
380
+ ) -> Optional[ObjectPack[Object]]:
381
+ """
382
+ 从池中借出一个对象。
383
+
384
+ Args:
385
+ timeout (Optional[Number | timedelta]): 借出对象的借出时间间隔,超过该时间后,对象将强制归还。
386
+ - Number: 单位:秒。
387
+ - timedelta: 经过该时间间隔被归还。
388
+ - None: 不限超时。
389
+ """
390
+ # 获取对象
391
+ await self.update_status_async()
392
+ available_object_wrappers = await self._get_object_wrappers_by_status_async(Status.AVAILABLE)
393
+ object_wrapper = await self._select_object_async(available_object_wrappers, **kwargs)
394
+ if object_wrapper is None:
395
+ # 没有可用对象
396
+ return None
397
+
398
+ # 计算到期时间
399
+ if timeout is None:
400
+ due_time = datetime.max
401
+ else:
402
+ if isinstance(timeout, Number):
403
+ timeout = timedelta(seconds = timeout)
404
+ due_time = datetime.now() + timeout
405
+
406
+ # 记录借出
407
+ object_wrapper.status = Status.BORROWED
408
+ self._due_datetime[id(object_wrapper.object)] = due_time
409
+
410
+ return await self._packer_async(object_wrapper)
411
+
412
+
413
+ # ==== 异步方法转同步 ====
414
+
415
+ def add(self, obj: Object, **kwargs) -> ObjectWrapper[Object]:
416
+ """ Synchronous version of `.add_async`. """
417
+ return asyncio.run(self.add_async(obj, **kwargs))
418
+
419
+ def discard(self, obj: Object) -> None:
420
+ """ Synchronous version of `.discard_async`. """
421
+ asyncio.run(self.discard_async(obj))
422
+
423
+ def remove(self, obj: Object) -> None:
424
+ """ Synchronous version of `.remove_async`. """
425
+ asyncio.run(self.remove_async(obj))
426
+
427
+ def clear(self) -> None:
428
+ """ Synchronous version of `.clear_async`. """
429
+ asyncio.run(self.clear_async())
430
+
431
+ def get_status(self, obj: Object) -> Status:
432
+ """ Synchronous version of `.get_status_async`. """
433
+ return asyncio.run(self.get_status_async(obj))
434
+
435
+ def update_status(self) -> None:
436
+ """ Synchronous version of `.update_status_async`. """
437
+ asyncio.run(self.update_status_async())
438
+
439
+ def borrow(self, timeout: Optional[Number | timedelta] = None, **kwargs) -> Optional[ObjectPack[Object]]:
440
+ """ Synchronous version of `.borrow_async`. """
441
+ return asyncio.run(self.borrow_async(timeout, **kwargs))
object_pool/status.py ADDED
@@ -0,0 +1,41 @@
1
+ """
2
+ enum: Status
3
+ """
4
+
5
+ try:
6
+ from enum import StrEnum
7
+ except ImportError:
8
+ # 兼容低版本
9
+ from enum import Enum
10
+ class StrEnum(str, Enum):
11
+ pass
12
+
13
+
14
+ class Status(StrEnum):
15
+ """
16
+ 池中对象的状态。
17
+
18
+ Note:
19
+ PENDING: 一般出现在:对象刚入池时、对象刚被归还时、对象借出超时后。
20
+ DORMANT: 对象目前不可用,但是过一段时间可能可用。
21
+ INVALID: 对象永久用不了。
22
+ UNKNOWN: 一般不会出现。
23
+ """
24
+
25
+ # 被借出的
26
+ BORROWED = "borrowed"
27
+
28
+ # 待检测的
29
+ PENDING = "pending"
30
+
31
+ # 可用的
32
+ AVAILABLE = "available"
33
+
34
+ # 休眠的
35
+ DORMANT = "dormant"
36
+
37
+ # 不可用的
38
+ INVALID = "invalid"
39
+
40
+ # 未知的
41
+ UNKNOWN = "unknown"
object_pool/utils.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ 提供了一些与同步、异步函数转换有关的工厂函数。
3
+
4
+ Pickle 友好。
5
+ """
6
+
7
+ import asyncio
8
+ from functools import partial
9
+ from inspect import isawaitable
10
+ from typing import Any, Awaitable, AsyncContextManager, Callable, ContextManager, Optional
11
+
12
+ # 同步函数类型
13
+ SyncFunction = Callable[..., Any]
14
+
15
+ # 异步函数类型
16
+ AsyncFunction = Callable[..., Awaitable[Any]]
17
+
18
+
19
+ async def _run_func_async(func: Callable, *args, **kwargs):
20
+ """
21
+ 异步运行一个同步或异步函数。
22
+ """
23
+ result = func(*args, **kwargs)
24
+ if isawaitable(result):
25
+ return await result
26
+ return result
27
+
28
+ def to_async(func: Callable) -> AsyncFunction:
29
+ """
30
+ 把同步或异步函数转换为异步函数。
31
+ """
32
+ return partial(_run_func_async, func)
33
+
34
+
35
+ def _run_func_sync(func: Callable, *args, **kwargs):
36
+ """
37
+ 同步运行一个同步或异步函数。
38
+ """
39
+ result = func(*args, **kwargs)
40
+ if isawaitable(result):
41
+ return asyncio.run(result)
42
+ return result
43
+
44
+ def to_sync(func: Callable) -> SyncFunction:
45
+ """
46
+ 把同步或异步函数转换为同步函数。
47
+ """
48
+ return partial(_run_func_sync, func)
49
+
50
+
51
+ def run_within_context(context: ContextManager, func: SyncFunction, *args, **kwargs):
52
+ """
53
+ 将函数 func 放在上下文里执行。
54
+ """
55
+ with context:
56
+ return func(*args, **kwargs)
57
+
58
+
59
+ async def run_within_context_async(context: AsyncContextManager, func: AsyncFunction, *args, **kwargs):
60
+ """
61
+ Asynchronous version of `run_within_context`.
62
+ """
63
+ async with context:
64
+ return await func(*args, **kwargs)
65
+
66
+
67
+ class AsyncRLock:
68
+ """简单的异步可重入锁,兼容 Python 3.10"""
69
+ def __init__(self):
70
+ self._lock = asyncio.Lock()
71
+ self._owner: Optional[int] = None # 持有锁的 task id
72
+ self._count = 0
73
+
74
+ async def acquire(self):
75
+ current_task = id(asyncio.current_task())
76
+ if self._owner == current_task:
77
+ self._count += 1
78
+ return True
79
+ await self._lock.acquire()
80
+ self._owner = current_task
81
+ self._count = 1
82
+ return True
83
+
84
+ def release(self):
85
+ if self._owner != id(asyncio.current_task()):
86
+ raise RuntimeError("Cannot release a lock that is not held by the current task")
87
+ self._count -= 1
88
+ if self._count == 0:
89
+ self._owner = None
90
+ self._lock.release()
91
+
92
+ async def __aenter__(self):
93
+ await self.acquire()
94
+ return self
95
+
96
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
97
+ self.release()
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: object-pool-async
3
+ Version: 0.1.2
4
+ Summary: A generic, async-first object pool for Python
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Jerry-Wu-GitHub/object-pool
8
+ Keywords: object-pool,async,connection-pool
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: license-file
21
+
22
+ # Object Pool
23
+
24
+ 一个功能丰富、支持异步的通用对象池,提供细粒度的对象生命周期管理、状态检测、超时回收和灵活的定制能力。
25
+
26
+ ## 特性
27
+
28
+ - ✅ **异步优先**:原生支持 `asyncio`,在高并发 I/O 场景下性能优异
29
+ - 🔄 **完整生命周期**:管理对象的 `PENDING`、`AVAILABLE`、`BORROWED`、`INVALID`、`DORMANT`、`UNKNOWN` 状态
30
+ - ⏱️ **超时回收**:支持对象级别的借出超时自动回收
31
+ - 🧩 **高度可定制**:可自定义状态检测器、对象选择器、包装器(Wrapper)和打包器(Packer)
32
+ - 🚦 **并发控制**:内置并发度限制,批量操作自动分块
33
+ - 🔁 **双 API**:同时提供异步(`*_async`)和同步接口
34
+ - 🧵 **协程安全**:基于 `AsyncRLock` 实现,在单事件循环多协程下安全
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ pip install object-pool
40
+ ```
41
+
42
+ 或从源码安装:
43
+
44
+ ```bash
45
+ git clone https://github.com/Jerry-Wu-GitHub/object-pool.git
46
+ cd object-pool
47
+ pip install -e .
48
+ ```
49
+
50
+ ## 快速开始
51
+
52
+ ### 同步使用
53
+
54
+ ```python
55
+ from object_pool import Pool, Status
56
+ from datetime import timedelta
57
+
58
+ # 定义一个简单的状态检测函数
59
+ def check_status(wrapper):
60
+ # 假设对象有一个 is_alive 属性
61
+ if wrapper.object.is_alive():
62
+ return Status.AVAILABLE
63
+ return Status.INVALID
64
+
65
+ # 创建对象池
66
+ pool = Pool(detect_status=check_status)
67
+
68
+ # 添加对象
69
+ obj = MyResource()
70
+ pool.add(obj)
71
+
72
+ # 借出对象
73
+ with pool.borrow() as resource:
74
+ resource.do_something()
75
+ # 离开上下文时自动归还
76
+
77
+ # 手动归还
78
+ pack = pool.borrow()
79
+ pack.close() # 归还
80
+ ```
81
+
82
+ ### 异步使用
83
+
84
+ ```python
85
+ import asyncio
86
+ from object_pool import Pool, Status
87
+
88
+ async def check_status_async(wrapper):
89
+ # 异步检测对象健康状态
90
+ if await wrapper.object.ping():
91
+ return Status.AVAILABLE
92
+ return Status.INVALID
93
+
94
+ async def main():
95
+ pool = Pool(detect_status=check_status_async)
96
+ obj = AsyncResource()
97
+ await pool.add_async(obj)
98
+
99
+ pack = await pool.borrow_async(timeout=3.0)
100
+ try:
101
+ await pack.object.work()
102
+ finally:
103
+ pack.close() # 归还
104
+
105
+ # 或使用异步上下文管理器
106
+ async with await pool.borrow_async() as resource:
107
+ await resource.work()
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## 高级用法
113
+
114
+ ### 自定义对象选择器
115
+
116
+ 默认使用随机选择。你可以实现自己的选择逻辑,例如轮询(Round-Robin)或[基于权重的选择](examples/weighted_selector.py):
117
+
118
+ ```python
119
+ def select_object(object_wrapers: List[ObjectWrapper[DummyObject]]) -> Optional[ObjectWrapper[DummyObject]]:
120
+ """
121
+ 按照权重选择一个对象。
122
+ """
123
+ if not object_wrapers:
124
+ return None
125
+ weights = [
126
+ object_wraper.weight
127
+ for object_wraper in object_wrapers
128
+ ]
129
+ return choices(object_wrapers, weights)[0]
130
+
131
+ # 注意:选择器可以是同步或异步函数,池内部会自动转换
132
+ ```
133
+
134
+ ### 定制包装器和打包器
135
+
136
+ ```python
137
+ class MyWrapper(ObjectWrapper):
138
+ def __init__(self, obj, **kwargs):
139
+ super().__init__(obj, **kwargs)
140
+ self.extra_info = kwargs.get('extra', {})
141
+
142
+ def my_packer(wrapper):
143
+ # 返回自定义的打包对象,可以增加额外行为
144
+ return MyPack(wrapper)
145
+
146
+ pool = Pool(detect_status=..., wrapper=MyWrapper, packer=my_packer)
147
+ ```
148
+
149
+ ### 使用默认数据
150
+
151
+ 通过 `wrapping_data` 为每个对象附加默认属性:
152
+
153
+ ```python
154
+ pool = Pool(..., wrapping_data={"created_at": datetime.now(), "source": "db"})
155
+ pool.add(obj) # ObjectWrapper 会自动包含 created_at 和 source
156
+ ```
157
+
158
+ ### 并发控制
159
+
160
+ `async_concurrency` 限制了同时执行状态检测或清理操作的并发任务数,避免瞬间压力过大:
161
+
162
+ ```python
163
+ pool = Pool(..., async_concurrency=10) # 最多同时检测10个对象
164
+ ```
165
+
166
+ ## 并发与线程安全
167
+
168
+ - **协程安全**:`Pool` 使用 `AsyncRLock` 保护关键操作,在**单个事件循环**内多协程并发访问是安全的。
169
+ - **线程安全**:**不保证**多线程安全。如果在多线程环境中使用同步方法(`add`、`borrow` 等),需要外部加锁(如 `threading.Lock`)。
170
+ - **建议**:在 `asyncio` 应用中始终使用异步 API;在传统多线程应用中请自行添加线程锁。
171
+
172
+ ## 依赖
173
+
174
+ - Python >= 3.10
175
+
176
+ ## 许可证
177
+
178
+ MIT
@@ -0,0 +1,11 @@
1
+ object_pool/__init__.py,sha256=iz_GmUjbrKgb1b1HmDQK1Y193nEw8PZrRAjuKB2tT_4,178
2
+ object_pool/collections.py,sha256=2L7nRssfVJ3HgcdtA5xdLISpMt-yC2WSrMytHneHTKc,814
3
+ object_pool/exceptions.py,sha256=pli0AzVykhjxMi5H1kaxXRzmAXMz2sR39EJ_wBn8TT0,200
4
+ object_pool/pool.py,sha256=dzSHHmoYF6lNR5CJUTm3qqtkPWHfpz7VD_X-HwPlvhg,14189
5
+ object_pool/status.py,sha256=TPxBp6MR_RogQrLynCA44SgQWH8UaYGILHcdCwGwt6w,814
6
+ object_pool/utils.py,sha256=aL8lDKE4ynVIZ6wVV1B2vE28UN0aK2rgZiBux381Fms,2684
7
+ object_pool_async-0.1.2.dist-info/licenses/LICENSE,sha256=errzu9jbnGCiEZ50jZxag9tkLf5opFvlVTDC8yfYlQA,1081
8
+ object_pool_async-0.1.2.dist-info/METADATA,sha256=JocB-D7BauA4hNETnR3JfEfXAgIBuJco3NJzLgInHww,5284
9
+ object_pool_async-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ object_pool_async-0.1.2.dist-info/top_level.txt,sha256=FEd7zgzY4-enb4703gEiD1UND-C343OBScF3aSdEcz8,12
11
+ object_pool_async-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jerry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ object_pool