queue-func 0.1__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 queue-func might be problematic. Click here for more details.
- queue_func/__init__.py +1 -0
- queue_func/broker/__init__.py +1 -0
- queue_func/broker/base.py +82 -0
- queue_func/broker/queue.py +44 -0
- queue_func/config/__init__.py +1 -0
- queue_func/config/general.py +32 -0
- queue_func/config/queue.py +14 -0
- queue_func/config/utils.py +15 -0
- queue_func/consumer/__init__.py +1 -0
- queue_func/consumer/base.py +97 -0
- queue_func/consumer/queue.py +55 -0
- queue_func/factory/__init__.py +12 -0
- queue_func/factory/backend.py +13 -0
- queue_func/factory/builders/__init__.py +9 -0
- queue_func/factory/builders/queue.py +20 -0
- queue_func/factory/deco.py +93 -0
- queue_func/factory/registry.py +41 -0
- queue_func/factory/runtime.py +40 -0
- queue_func/message/__init__.py +1 -0
- queue_func/message/base.py +39 -0
- queue_func/message/general.py +30 -0
- queue_func/pool_executor/__init__.py +1 -0
- queue_func/pool_executor/base.py +31 -0
- queue_func/pool_executor/general.py +42 -0
- queue_func/pool_executor/utils.py +49 -0
- queue_func/producer/__init__.py +1 -0
- queue_func/producer/base.py +36 -0
- queue_func/producer/queue.py +30 -0
- queue_func/serializer/__init__.py +1 -0
- queue_func/serializer/base.py +38 -0
- queue_func/serializer/json_ser.py +25 -0
- queue_func/serializer/orjson_ser.py +30 -0
- queue_func/serializer/utils.py +16 -0
- queue_func/task/__init__.py +1 -0
- queue_func/task/base.py +28 -0
- queue_func/task/thread.py +26 -0
- queue_func-0.1.dist-info/METADATA +121 -0
- queue_func-0.1.dist-info/RECORD +40 -0
- queue_func-0.1.dist-info/WHEEL +4 -0
- queue_func-0.1.dist-info/licenses/LICENSE +201 -0
queue_func/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from queue_func.message.base import BaseMessage, Packed
|
|
7
|
+
|
|
8
|
+
if t.TYPE_CHECKING:
|
|
9
|
+
from queue_func.config.general import Config
|
|
10
|
+
from queue_func.producer.base import BaseProducer
|
|
11
|
+
from queue_func.consumer.base import BaseConsumer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ConfigType = t.TypeVar("ConfigType", bound="Config")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseBroker(t.Generic[ConfigType, Packed], metaclass=abc.ABCMeta):
|
|
18
|
+
"""一条通道的装配者。
|
|
19
|
+
|
|
20
|
+
创建时准备好双方共用的 Message,并持有配置与 Producer/Consumer。
|
|
21
|
+
对外通过 publish、start_consumer 等入口操作;运行期业务逻辑仍在 Producer/Consumer 内完成。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__slots__ = (
|
|
25
|
+
"_config",
|
|
26
|
+
"_message",
|
|
27
|
+
"_producer",
|
|
28
|
+
"_consumer",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def __init__(self, config: ConfigType):
|
|
32
|
+
|
|
33
|
+
self._config = config
|
|
34
|
+
self._message: BaseMessage[Packed] = self._config.message_cls(
|
|
35
|
+
message_serializer_cls=self._config.message_serializer_cls,
|
|
36
|
+
enable_serializer=self._config.enable_message_serializer,
|
|
37
|
+
)
|
|
38
|
+
self._producer = self._create_producer()
|
|
39
|
+
self._consumer = self._create_consumer()
|
|
40
|
+
|
|
41
|
+
@abc.abstractmethod
|
|
42
|
+
def _create_producer(self) -> "BaseProducer[Packed]":
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abc.abstractmethod
|
|
47
|
+
def _create_consumer(self) -> "BaseConsumer[Packed]":
|
|
48
|
+
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def message(self) -> BaseMessage[Packed]:
|
|
53
|
+
|
|
54
|
+
return self._message
|
|
55
|
+
|
|
56
|
+
async def publish(
|
|
57
|
+
self,
|
|
58
|
+
coro_args: tuple[t.Any, ...],
|
|
59
|
+
coro_kwargs: dict[str, t.Any] | None = None,
|
|
60
|
+
**kwargs: t.Any) -> None:
|
|
61
|
+
|
|
62
|
+
await self._producer.publish(coro_args, coro_kwargs, **kwargs)
|
|
63
|
+
|
|
64
|
+
async def start_consumer(self) -> None:
|
|
65
|
+
|
|
66
|
+
await self._consumer.consume()
|
|
67
|
+
|
|
68
|
+
async def stop_consumer(self) -> None:
|
|
69
|
+
|
|
70
|
+
await self._consumer.stop()
|
|
71
|
+
|
|
72
|
+
async def join_consumer(self) -> None:
|
|
73
|
+
|
|
74
|
+
await self._consumer.join()
|
|
75
|
+
|
|
76
|
+
async def stop_producer(self) -> None:
|
|
77
|
+
|
|
78
|
+
await self._producer.stop()
|
|
79
|
+
|
|
80
|
+
async def join_producer(self) -> None:
|
|
81
|
+
|
|
82
|
+
await self._producer.join()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from queue_func.message.base import Packed
|
|
7
|
+
from queue_func.broker.base import BaseBroker as _BaseBroker
|
|
8
|
+
from queue_func.producer.queue import Producer
|
|
9
|
+
from queue_func.consumer.queue import Consumer
|
|
10
|
+
|
|
11
|
+
if t.TYPE_CHECKING:
|
|
12
|
+
from queue_func.config.queue import Config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Broker(_BaseBroker["Config", Packed]):
|
|
16
|
+
"""内存队列通道。队列由这里创建,再交给 Producer 和 Consumer。"""
|
|
17
|
+
|
|
18
|
+
__slots__ = (
|
|
19
|
+
"_queue",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: "Config"):
|
|
23
|
+
self._queue: asyncio.Queue[Packed] = asyncio.Queue(maxsize=config.maxsize)
|
|
24
|
+
super().__init__(config)
|
|
25
|
+
|
|
26
|
+
def _create_producer(self) -> Producer[Packed]:
|
|
27
|
+
|
|
28
|
+
return Producer(
|
|
29
|
+
queue=self._queue,
|
|
30
|
+
publish_timeout=self._config.publish_timeout,
|
|
31
|
+
message=self._message,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _create_consumer(self) -> Consumer[Packed]:
|
|
35
|
+
|
|
36
|
+
return Consumer(
|
|
37
|
+
queue=self._queue,
|
|
38
|
+
consume_timeout=self._config.consume_timeout,
|
|
39
|
+
message=self._message,
|
|
40
|
+
coro=self._config.coro,
|
|
41
|
+
enable_consumer=self._config.enable_consumer,
|
|
42
|
+
pool_executor_cls=self._config.consumer_pool_executor_cls,
|
|
43
|
+
pool_maxsize=self._config.consumer_pool_maxsize,
|
|
44
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from pydantic.dataclasses import dataclass as pyd_dataclass
|
|
6
|
+
|
|
7
|
+
from queue_func.config.utils import ExtraIgnoreConfig
|
|
8
|
+
from queue_func.message.base import BaseMessage
|
|
9
|
+
from queue_func.message.general import Message
|
|
10
|
+
from queue_func.serializer.base import BaseSerializer
|
|
11
|
+
from queue_func.serializer.orjson_ser import OrJSONSerializer
|
|
12
|
+
from queue_func.pool_executor.base import BasePoolExecutor
|
|
13
|
+
from queue_func.pool_executor.general import PoolExecutor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig)
|
|
17
|
+
class Config:
|
|
18
|
+
|
|
19
|
+
# consumer
|
|
20
|
+
coro: t.Callable[[...], t.Coroutine]
|
|
21
|
+
enable_consumer: bool = True
|
|
22
|
+
consume_timeout: float = 2.0
|
|
23
|
+
consumer_pool_executor_cls: type[BasePoolExecutor] = PoolExecutor
|
|
24
|
+
consumer_pool_maxsize: int = 200
|
|
25
|
+
|
|
26
|
+
# broker
|
|
27
|
+
message_cls: type[BaseMessage] = Message
|
|
28
|
+
message_serializer_cls: type[BaseSerializer] = OrJSONSerializer
|
|
29
|
+
enable_message_serializer: bool = True
|
|
30
|
+
|
|
31
|
+
# producer
|
|
32
|
+
publish_timeout: float | None = None
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
from pydantic.dataclasses import dataclass as pyd_dataclass
|
|
5
|
+
|
|
6
|
+
from queue_func.config.utils import ExtraIgnoreConfig
|
|
7
|
+
from queue_func.config.general import Config as _Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig)
|
|
11
|
+
class Config(_Config):
|
|
12
|
+
|
|
13
|
+
# broker
|
|
14
|
+
maxsize: int = 200
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
from pydantic.config import ConfigDict
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
"ExtraIgnoreConfig",
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ExtraIgnoreConfig = ConfigDict(
|
|
11
|
+
extra="ignore",
|
|
12
|
+
validate_assignment=True,
|
|
13
|
+
arbitrary_types_allowed=True,
|
|
14
|
+
coerce_numbers_to_str=True,
|
|
15
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import contextlib
|
|
7
|
+
import typing as t
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
from queue_func.message.base import BaseMessage, Packed
|
|
11
|
+
from queue_func.pool_executor.base import BasePoolExecutor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseConsumer(t.Generic[Packed], metaclass=abc.ABCMeta):
|
|
15
|
+
|
|
16
|
+
__slots__ = (
|
|
17
|
+
"_message",
|
|
18
|
+
"_coro",
|
|
19
|
+
"_enable_consumer",
|
|
20
|
+
"_pool_executor",
|
|
21
|
+
"_task",
|
|
22
|
+
"_stop_flag",
|
|
23
|
+
"_logger",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
message: BaseMessage[Packed],
|
|
29
|
+
coro: t.Callable[..., t.Coroutine],
|
|
30
|
+
enable_consumer: bool,
|
|
31
|
+
pool_executor_cls: type[BasePoolExecutor],
|
|
32
|
+
pool_maxsize: int):
|
|
33
|
+
|
|
34
|
+
self._message = message
|
|
35
|
+
self._coro = coro
|
|
36
|
+
self._enable_consumer = enable_consumer
|
|
37
|
+
self._pool_executor = self._init_pool_executor(pool_executor_cls, pool_maxsize)
|
|
38
|
+
self._task: asyncio.Task | None = None
|
|
39
|
+
self._stop_flag = False
|
|
40
|
+
self._logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
def _init_pool_executor(
|
|
43
|
+
self,
|
|
44
|
+
consumer_pool_executor_cls: type[BasePoolExecutor],
|
|
45
|
+
consumer_pool_maxsize: int) -> BasePoolExecutor | None:
|
|
46
|
+
|
|
47
|
+
if self._enable_consumer:
|
|
48
|
+
return consumer_pool_executor_cls(consumer_pool_maxsize)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def _run_until_stopped(
|
|
52
|
+
self,
|
|
53
|
+
coro: t.Callable[..., t.Awaitable],
|
|
54
|
+
delay: float = 0.02) -> t.Callable[..., t.Coroutine]:
|
|
55
|
+
|
|
56
|
+
@wraps(coro)
|
|
57
|
+
async def wrapped(*args, **kwargs) -> None:
|
|
58
|
+
|
|
59
|
+
while not self._stop_flag:
|
|
60
|
+
try:
|
|
61
|
+
await coro(*args, **kwargs)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
self._logger.exception(e)
|
|
64
|
+
await asyncio.sleep(delay)
|
|
65
|
+
|
|
66
|
+
return wrapped
|
|
67
|
+
|
|
68
|
+
async def _dispatch(self, coro_args: tuple[t.Any], coro_kwargs: dict[str, t.Any], **_kwargs) -> None:
|
|
69
|
+
|
|
70
|
+
assert self._pool_executor is not None, "consumer pool not initialized"
|
|
71
|
+
await self._pool_executor.submit(self._coro, coro_args, coro_kwargs)
|
|
72
|
+
|
|
73
|
+
async def consume(self) -> None:
|
|
74
|
+
|
|
75
|
+
if self._enable_consumer:
|
|
76
|
+
self._stop_flag = False
|
|
77
|
+
consume_loop = self._run_until_stopped(self._consume)
|
|
78
|
+
self._task = asyncio.create_task(consume_loop())
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
@abc.abstractmethod
|
|
82
|
+
async def _consume(self) -> None: # pragma: no cover
|
|
83
|
+
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
async def stop(self) -> None:
|
|
87
|
+
|
|
88
|
+
self._stop_flag = True
|
|
89
|
+
|
|
90
|
+
async def join(self) -> None:
|
|
91
|
+
|
|
92
|
+
if self._task is not None:
|
|
93
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
94
|
+
await self._task
|
|
95
|
+
|
|
96
|
+
if self._pool_executor is not None:
|
|
97
|
+
await self._pool_executor.join()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from queue_func.message.base import BaseMessage, Packed
|
|
7
|
+
from queue_func.pool_executor.base import BasePoolExecutor
|
|
8
|
+
from queue_func.consumer.base import BaseConsumer as _BaseConsumer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Consumer(_BaseConsumer[Packed]):
|
|
12
|
+
|
|
13
|
+
__slots__ = (
|
|
14
|
+
"_queue",
|
|
15
|
+
"_consume_timeout",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
queue: asyncio.Queue[Packed],
|
|
21
|
+
consume_timeout: float,
|
|
22
|
+
message: BaseMessage[Packed],
|
|
23
|
+
coro: t.Callable[..., t.Coroutine],
|
|
24
|
+
enable_consumer: bool,
|
|
25
|
+
pool_executor_cls: type[BasePoolExecutor],
|
|
26
|
+
pool_maxsize: int):
|
|
27
|
+
|
|
28
|
+
super().__init__(
|
|
29
|
+
message,
|
|
30
|
+
coro,
|
|
31
|
+
enable_consumer,
|
|
32
|
+
pool_executor_cls,
|
|
33
|
+
pool_maxsize,
|
|
34
|
+
)
|
|
35
|
+
self._queue = queue
|
|
36
|
+
self._consume_timeout = consume_timeout
|
|
37
|
+
|
|
38
|
+
async def _consume(self) -> None:
|
|
39
|
+
|
|
40
|
+
while (not self._stop_flag) or (not self._queue.empty()):
|
|
41
|
+
try:
|
|
42
|
+
message: Packed = await asyncio.wait_for(
|
|
43
|
+
self._queue.get(),
|
|
44
|
+
timeout=self._consume_timeout,
|
|
45
|
+
)
|
|
46
|
+
except asyncio.TimeoutError:
|
|
47
|
+
continue
|
|
48
|
+
coro_args, coro_kwargs = self._message.unpack(message)
|
|
49
|
+
await self._dispatch(coro_args, coro_kwargs)
|
|
50
|
+
self._queue.task_done()
|
|
51
|
+
|
|
52
|
+
async def join(self) -> None:
|
|
53
|
+
|
|
54
|
+
await super().join()
|
|
55
|
+
await self._queue.join()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
from queue_func.factory.backend import Backend
|
|
4
|
+
from queue_func.factory.deco import QueueFunc, queue
|
|
5
|
+
from queue_func.factory.runtime import QueueFuncRuntime
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"Backend",
|
|
9
|
+
"QueueFunc",
|
|
10
|
+
"QueueFuncRuntime",
|
|
11
|
+
"queue",
|
|
12
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.factory.backend import Backend
|
|
6
|
+
from queue_func.factory.registry import register_backend
|
|
7
|
+
|
|
8
|
+
if t.TYPE_CHECKING:
|
|
9
|
+
from queue_func.broker.base import BaseBroker
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register_backend(Backend.QUEUE)
|
|
13
|
+
def build_queue_broker(
|
|
14
|
+
coro: t.Callable[..., t.Coroutine],
|
|
15
|
+
**kwargs: t.Any) -> "BaseBroker[t.Any, t.Any]":
|
|
16
|
+
|
|
17
|
+
from queue_func.broker.queue import Broker
|
|
18
|
+
from queue_func.config.queue import Config
|
|
19
|
+
|
|
20
|
+
return Broker(Config(coro=coro, **kwargs))
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.factory import builders as _builders # noqa: F401 — register backends
|
|
6
|
+
from queue_func.factory.backend import Backend
|
|
7
|
+
from queue_func.factory.registry import build_broker
|
|
8
|
+
|
|
9
|
+
if t.TYPE_CHECKING:
|
|
10
|
+
from queue_func.broker.base import BaseBroker
|
|
11
|
+
from queue_func.config.general import Config as GeneralConfig
|
|
12
|
+
from queue_func.message.base import BaseMessage
|
|
13
|
+
from queue_func.pool_executor.base import BasePoolExecutor
|
|
14
|
+
from queue_func.serializer.base import BaseSerializer
|
|
15
|
+
|
|
16
|
+
_P = t.ParamSpec("_P")
|
|
17
|
+
_R = t.TypeVar("_R")
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"QueueFunc",
|
|
21
|
+
"queue",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class QueueFunc(t.Generic[_P, _R]):
|
|
26
|
+
"""可调用协程,并附带 push。"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
fn: t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]],
|
|
31
|
+
broker: "BaseBroker[t.Any, t.Any]") -> None:
|
|
32
|
+
|
|
33
|
+
self._fn = fn
|
|
34
|
+
self._broker = broker
|
|
35
|
+
self.__wrapped__ = fn
|
|
36
|
+
self.__name__ = fn.__name__
|
|
37
|
+
self.__qualname__ = fn.__qualname__
|
|
38
|
+
self.__doc__ = fn.__doc__
|
|
39
|
+
self.__annotations__ = getattr(fn, "__annotations__", {})
|
|
40
|
+
|
|
41
|
+
async def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
42
|
+
|
|
43
|
+
return await self._fn(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
async def push(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
args: tuple[t.Any, ...] = (),
|
|
49
|
+
kwargs: dict[str, t.Any] | None = None) -> None:
|
|
50
|
+
|
|
51
|
+
await self._broker.publish(coro_args=args, coro_kwargs=kwargs)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def broker(self) -> "BaseBroker[t.Any, t.Any]":
|
|
55
|
+
|
|
56
|
+
return self._broker
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _queue_impl(
|
|
60
|
+
backend: Backend,
|
|
61
|
+
**config_kwargs: t.Any) -> t.Callable[[t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]], QueueFunc[_P, _R]]:
|
|
62
|
+
|
|
63
|
+
def decorator(fn: t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]) -> QueueFunc[_P, _R]:
|
|
64
|
+
|
|
65
|
+
broker = build_broker(backend, fn, **config_kwargs)
|
|
66
|
+
return QueueFunc(fn, broker)
|
|
67
|
+
|
|
68
|
+
return decorator
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@t.overload
|
|
72
|
+
def queue(
|
|
73
|
+
backend: t.Literal[Backend.QUEUE],
|
|
74
|
+
*,
|
|
75
|
+
enable_consumer: bool = ...,
|
|
76
|
+
consume_timeout: float = ...,
|
|
77
|
+
consumer_pool_executor_cls: type["BasePoolExecutor"] = ...,
|
|
78
|
+
consumer_pool_maxsize: int = ...,
|
|
79
|
+
message_cls: type["BaseMessage"] = ...,
|
|
80
|
+
message_serializer_cls: type["BaseSerializer"] = ...,
|
|
81
|
+
enable_message_serializer: bool = ...,
|
|
82
|
+
publish_timeout: float | None = ...,
|
|
83
|
+
maxsize: int = ...,
|
|
84
|
+
) -> t.Callable[[t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]], QueueFunc[_P, _R]]:
|
|
85
|
+
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def queue(
|
|
90
|
+
backend: Backend,
|
|
91
|
+
**config_kwargs: t.Any) -> t.Callable[[t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]], QueueFunc[_P, _R]]:
|
|
92
|
+
|
|
93
|
+
return _queue_impl(backend, **config_kwargs)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.factory.backend import Backend
|
|
6
|
+
|
|
7
|
+
if t.TYPE_CHECKING:
|
|
8
|
+
from queue_func.broker.base import BaseBroker
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"register_backend",
|
|
12
|
+
"build_broker",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
BrokerBuilder = t.Callable[..., "BaseBroker[t.Any, t.Any]"]
|
|
16
|
+
|
|
17
|
+
_BUILDERS: dict[Backend, BrokerBuilder] = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register_backend(backend: Backend) -> t.Callable[[BrokerBuilder], BrokerBuilder]:
|
|
21
|
+
|
|
22
|
+
def decorator(fn: BrokerBuilder) -> BrokerBuilder:
|
|
23
|
+
|
|
24
|
+
if backend in _BUILDERS:
|
|
25
|
+
raise ValueError(f"backend {backend!r} is already registered")
|
|
26
|
+
_BUILDERS[backend] = fn
|
|
27
|
+
return fn
|
|
28
|
+
|
|
29
|
+
return decorator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_broker(
|
|
33
|
+
backend: Backend,
|
|
34
|
+
coro: t.Callable[..., t.Coroutine],
|
|
35
|
+
**kwargs: t.Any) -> "BaseBroker[t.Any, t.Any]":
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
builder = _BUILDERS[backend]
|
|
39
|
+
except KeyError:
|
|
40
|
+
raise NotImplementedError(f"backend {backend!r} is not implemented yet") from None
|
|
41
|
+
return builder(coro, **kwargs)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.factory.deco import QueueFunc
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"QueueFuncRuntime",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueueFuncRuntime:
|
|
13
|
+
"""按固定顺序批量启动 / 关闭多个 :class:`QueueFunc`。
|
|
14
|
+
|
|
15
|
+
stop:consumer 逐个 stop → join;producer 先全体 stop,再全体 join。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__slots__ = (
|
|
19
|
+
"_funcs",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __init__(self, funcs: t.Iterable[QueueFunc[t.Any, t.Any]]) -> None:
|
|
23
|
+
|
|
24
|
+
self._funcs = list(funcs)
|
|
25
|
+
|
|
26
|
+
async def start(self) -> None:
|
|
27
|
+
|
|
28
|
+
for func in reversed(self._funcs):
|
|
29
|
+
await func.broker.start_consumer()
|
|
30
|
+
|
|
31
|
+
async def stop(self) -> None:
|
|
32
|
+
|
|
33
|
+
for func in self._funcs:
|
|
34
|
+
await func.broker.stop_consumer()
|
|
35
|
+
await func.broker.join_consumer()
|
|
36
|
+
|
|
37
|
+
for func in self._funcs:
|
|
38
|
+
await func.broker.stop_producer()
|
|
39
|
+
for func in self._funcs:
|
|
40
|
+
await func.broker.join_producer()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from queue_func.serializer.base import BaseSerializer
|
|
7
|
+
|
|
8
|
+
Packed = t.TypeVar("Packed")
|
|
9
|
+
|
|
10
|
+
type ArgsMessage = tuple[tuple[t.Any], dict[str, t.Any]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseMessage(t.Generic[Packed], metaclass=abc.ABCMeta):
|
|
14
|
+
|
|
15
|
+
__slots__ = (
|
|
16
|
+
"_enable_serializer",
|
|
17
|
+
"_serializer",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
message_serializer_cls: type[BaseSerializer],
|
|
23
|
+
enable_serializer: bool):
|
|
24
|
+
|
|
25
|
+
self._serializer = message_serializer_cls()
|
|
26
|
+
self._enable_serializer = enable_serializer
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def pack(
|
|
30
|
+
self,
|
|
31
|
+
coro_args: tuple[t.Any],
|
|
32
|
+
coro_kwargs: dict[str, t.Any] | None = None) -> Packed:
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def unpack(self, message: Packed) -> ArgsMessage:
|
|
38
|
+
|
|
39
|
+
pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.message.base import ArgsMessage, BaseMessage
|
|
6
|
+
|
|
7
|
+
type PackedMessage = ArgsMessage | bytes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Message(BaseMessage[PackedMessage]):
|
|
11
|
+
|
|
12
|
+
__slots__ = ()
|
|
13
|
+
|
|
14
|
+
def pack(
|
|
15
|
+
self,
|
|
16
|
+
coro_args: tuple[t.Any],
|
|
17
|
+
coro_kwargs: dict[str, t.Any] | None = None) -> PackedMessage:
|
|
18
|
+
|
|
19
|
+
if coro_kwargs is None:
|
|
20
|
+
coro_kwargs = {}
|
|
21
|
+
message: ArgsMessage = (coro_args, coro_kwargs)
|
|
22
|
+
if self._enable_serializer:
|
|
23
|
+
return self._serializer.dumps(message, return_bytes=True)
|
|
24
|
+
return message
|
|
25
|
+
|
|
26
|
+
def unpack(self, message: PackedMessage) -> ArgsMessage:
|
|
27
|
+
|
|
28
|
+
if self._enable_serializer:
|
|
29
|
+
return t.cast(ArgsMessage, self._serializer.loads(message))
|
|
30
|
+
return t.cast(ArgsMessage, message)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# coding: utf-8
|