queue-func 0.1__py3-none-any.whl → 0.2.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.
- queue_func/__init__.py +6 -0
- queue_func/broker/base.py +9 -31
- queue_func/broker/queue.py +16 -26
- queue_func/broker/rabbit_aio_pika.py +47 -0
- queue_func/config/general.py +7 -11
- queue_func/config/queue.py +4 -1
- queue_func/config/rabbit_aio_pika.py +55 -0
- queue_func/consumer/base.py +38 -22
- queue_func/consumer/queue.py +12 -35
- queue_func/consumer/rabbit_aio_pika.py +99 -0
- queue_func/errors.py +7 -0
- queue_func/factory/backend.py +1 -0
- queue_func/factory/builders/__init__.py +2 -0
- queue_func/factory/builders/rabbit_aio_pika.py +26 -0
- queue_func/factory/deco.py +34 -6
- queue_func/producer/base.py +44 -11
- queue_func/producer/queue.py +12 -21
- queue_func/producer/rabbit_aio_pika.py +83 -0
- queue_func/serializer/base.py +10 -21
- queue_func/serializer/orjson_ser.py +14 -17
- queue_func/serializer/raw.py +24 -0
- queue_func/shared_resource/base.py +25 -0
- queue_func/shared_resource/queue.py +23 -0
- queue_func/shared_resource/rabbit_aio_pika.py +111 -0
- queue_func/utils/__init__.py +1 -0
- queue_func/utils/deco.py +36 -0
- queue_func/utils/hook.py +50 -0
- queue_func/utils/waiter.py +35 -0
- queue_func-0.2.1.dist-info/METADATA +255 -0
- queue_func-0.2.1.dist-info/RECORD +50 -0
- queue_func/message/base.py +0 -39
- queue_func/message/general.py +0 -30
- queue_func/serializer/json_ser.py +0 -25
- queue_func/serializer/utils.py +0 -16
- queue_func-0.1.dist-info/METADATA +0 -121
- queue_func-0.1.dist-info/RECORD +0 -40
- /queue_func/{message → shared_resource}/__init__.py +0 -0
- {queue_func-0.1.dist-info → queue_func-0.2.1.dist-info}/WHEEL +0 -0
- {queue_func-0.1.dist-info → queue_func-0.2.1.dist-info}/licenses/LICENSE +0 -0
queue_func/__init__.py
CHANGED
queue_func/broker/base.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import abc
|
|
4
4
|
import typing as t
|
|
5
5
|
|
|
6
|
-
from queue_func.
|
|
6
|
+
from queue_func.serializer.base import Message
|
|
7
|
+
from queue_func.shared_resource.base import Resource
|
|
7
8
|
|
|
8
9
|
if t.TYPE_CHECKING:
|
|
9
10
|
from queue_func.config.general import Config
|
|
@@ -14,52 +15,29 @@ if t.TYPE_CHECKING:
|
|
|
14
15
|
ConfigType = t.TypeVar("ConfigType", bound="Config")
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class BaseBroker(t.Generic[ConfigType,
|
|
18
|
+
class BaseBroker(t.Generic[ConfigType, Message], metaclass=abc.ABCMeta):
|
|
18
19
|
"""一条通道的装配者。
|
|
19
20
|
|
|
20
|
-
创建时准备好双方共用的
|
|
21
|
+
创建时准备好双方共用的 Serializer,并持有配置与 Producer/Consumer。
|
|
21
22
|
对外通过 publish、start_consumer 等入口操作;运行期业务逻辑仍在 Producer/Consumer 内完成。
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
25
|
__slots__ = (
|
|
25
|
-
"_config",
|
|
26
|
-
"_message",
|
|
27
26
|
"_producer",
|
|
28
27
|
"_consumer",
|
|
29
28
|
)
|
|
30
29
|
|
|
31
|
-
|
|
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
|
|
30
|
+
_producer: "BaseProducer"
|
|
31
|
+
_consumer: "BaseConsumer"
|
|
55
32
|
|
|
56
33
|
async def publish(
|
|
57
34
|
self,
|
|
58
35
|
coro_args: tuple[t.Any, ...],
|
|
59
36
|
coro_kwargs: dict[str, t.Any] | None = None,
|
|
60
|
-
|
|
37
|
+
*,
|
|
38
|
+
timeout: float | None = None) -> None:
|
|
61
39
|
|
|
62
|
-
await self._producer.publish(coro_args, coro_kwargs,
|
|
40
|
+
await self._producer.publish(coro_args, coro_kwargs, timeout=timeout)
|
|
63
41
|
|
|
64
42
|
async def start_consumer(self) -> None:
|
|
65
43
|
|
queue_func/broker/queue.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import typing as t
|
|
5
5
|
|
|
6
|
-
from queue_func.
|
|
6
|
+
from queue_func.serializer.base import Message
|
|
7
|
+
from queue_func.shared_resource.queue import Resource
|
|
7
8
|
from queue_func.broker.base import BaseBroker as _BaseBroker
|
|
8
9
|
from queue_func.producer.queue import Producer
|
|
9
10
|
from queue_func.consumer.queue import Consumer
|
|
@@ -12,33 +13,22 @@ if t.TYPE_CHECKING:
|
|
|
12
13
|
from queue_func.config.queue import Config
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class Broker(_BaseBroker["Config",
|
|
16
|
+
class Broker(_BaseBroker["Config", Message]):
|
|
16
17
|
"""内存队列通道。队列由这里创建,再交给 Producer 和 Consumer。"""
|
|
17
18
|
|
|
18
|
-
__slots__ = (
|
|
19
|
-
"_queue",
|
|
20
|
-
)
|
|
21
|
-
|
|
22
19
|
def __init__(self, config: "Config"):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
queue=self._queue,
|
|
30
|
-
publish_timeout=self._config.publish_timeout,
|
|
31
|
-
message=self._message,
|
|
20
|
+
resource = Resource(
|
|
21
|
+
serializer_cls=config.serializer_cls,
|
|
22
|
+
maxsize=config.maxsize)
|
|
23
|
+
self._producer = Producer(
|
|
24
|
+
resource=resource,
|
|
25
|
+
publish_timeout=config.publish_timeout,
|
|
32
26
|
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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,
|
|
27
|
+
self._consumer = Consumer(
|
|
28
|
+
resource=resource,
|
|
29
|
+
coro=config.coro,
|
|
30
|
+
enable_consumer=config.enable_consumer,
|
|
31
|
+
consume_timeout=config.consume_timeout,
|
|
32
|
+
pool_executor_cls=config.consumer_pool_executor_cls,
|
|
33
|
+
pool_maxsize=config.consumer_pool_maxsize,
|
|
44
34
|
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from queue_func.broker.base import BaseBroker as _BaseBroker
|
|
6
|
+
from queue_func.consumer.rabbit_aio_pika import Consumer
|
|
7
|
+
from queue_func.producer.rabbit_aio_pika import Producer
|
|
8
|
+
from queue_func.shared_resource.rabbit_aio_pika import Resource
|
|
9
|
+
|
|
10
|
+
if t.TYPE_CHECKING:
|
|
11
|
+
from queue_func.config.rabbit_aio_pika import Config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Broker(_BaseBroker["Config", bytes]):
|
|
15
|
+
"""RabbitMQ 通道。一条连接上分开发布和消费,再交给 Producer 和 Consumer。"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: "Config"):
|
|
18
|
+
|
|
19
|
+
resource = Resource(
|
|
20
|
+
serializer_cls=config.serializer_cls,
|
|
21
|
+
url=config.url,
|
|
22
|
+
queue_name=config.queue_name,
|
|
23
|
+
enable_declare_queue=config.enable_declare_queue,
|
|
24
|
+
queue_durable=config.queue_durable,
|
|
25
|
+
queue_exclusive=config.queue_exclusive,
|
|
26
|
+
queue_passive=config.queue_passive,
|
|
27
|
+
queue_auto_delete=config.queue_auto_delete,
|
|
28
|
+
queue_arguments=config.queue_arguments,
|
|
29
|
+
)
|
|
30
|
+
self._producer = Producer(
|
|
31
|
+
resource=resource,
|
|
32
|
+
publish_timeout=config.publish_timeout,
|
|
33
|
+
exchange_name=config.exchange_name,
|
|
34
|
+
routing_key=config.routing_key,
|
|
35
|
+
delivery_mode=config.delivery_mode,
|
|
36
|
+
)
|
|
37
|
+
self._consumer = Consumer(
|
|
38
|
+
resource=resource,
|
|
39
|
+
prefetch_count=config.prefetch_count,
|
|
40
|
+
prefetch_size=config.prefetch_size,
|
|
41
|
+
global_qos=config.global_qos,
|
|
42
|
+
coro=config.coro,
|
|
43
|
+
enable_consumer=config.enable_consumer,
|
|
44
|
+
consume_timeout=config.consume_timeout,
|
|
45
|
+
pool_executor_cls=config.consumer_pool_executor_cls,
|
|
46
|
+
pool_maxsize=config.consumer_pool_maxsize,
|
|
47
|
+
)
|
queue_func/config/general.py
CHANGED
|
@@ -5,28 +5,24 @@ import typing as t
|
|
|
5
5
|
from pydantic.dataclasses import dataclass as pyd_dataclass
|
|
6
6
|
|
|
7
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
8
|
from queue_func.serializer.base import BaseSerializer
|
|
11
|
-
from queue_func.serializer.orjson_ser import
|
|
9
|
+
from queue_func.serializer.orjson_ser import Serializer
|
|
12
10
|
from queue_func.pool_executor.base import BasePoolExecutor
|
|
13
11
|
from queue_func.pool_executor.general import PoolExecutor
|
|
14
12
|
|
|
15
13
|
|
|
16
|
-
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig)
|
|
14
|
+
@pyd_dataclass(frozen=True, kw_only=True, config=ExtraIgnoreConfig)
|
|
17
15
|
class Config:
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
## shared resource
|
|
18
|
+
serializer_cls: type[BaseSerializer] = Serializer
|
|
19
|
+
|
|
20
|
+
## consumer
|
|
20
21
|
coro: t.Callable[[...], t.Coroutine]
|
|
21
22
|
enable_consumer: bool = True
|
|
22
23
|
consume_timeout: float = 2.0
|
|
23
24
|
consumer_pool_executor_cls: type[BasePoolExecutor] = PoolExecutor
|
|
24
25
|
consumer_pool_maxsize: int = 200
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
message_cls: type[BaseMessage] = Message
|
|
28
|
-
message_serializer_cls: type[BaseSerializer] = OrJSONSerializer
|
|
29
|
-
enable_message_serializer: bool = True
|
|
30
|
-
|
|
31
|
-
# producer
|
|
27
|
+
## producer
|
|
32
28
|
publish_timeout: float | None = None
|
queue_func/config/queue.py
CHANGED
|
@@ -5,10 +5,13 @@ from pydantic.dataclasses import dataclass as pyd_dataclass
|
|
|
5
5
|
|
|
6
6
|
from queue_func.config.utils import ExtraIgnoreConfig
|
|
7
7
|
from queue_func.config.general import Config as _Config
|
|
8
|
+
from queue_func.serializer.base import BaseSerializer
|
|
9
|
+
from queue_func.serializer.raw import Serializer
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig)
|
|
11
13
|
class Config(_Config):
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## shared resource
|
|
14
16
|
maxsize: int = 200
|
|
17
|
+
serializer_cls: type[BaseSerializer] = Serializer
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
from enum import unique, StrEnum, IntEnum
|
|
4
|
+
|
|
5
|
+
from pydantic.dataclasses import dataclass as pyd_dataclass
|
|
6
|
+
|
|
7
|
+
from queue_func.config.utils import ExtraIgnoreConfig
|
|
8
|
+
from queue_func.config.general import Config as _Config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@unique
|
|
12
|
+
class ExchangeName(StrEnum):
|
|
13
|
+
|
|
14
|
+
DEFAULT = ""
|
|
15
|
+
DIRECT = "amq.direct"
|
|
16
|
+
TOPIC = "amq.topic"
|
|
17
|
+
FANOUT = "amq.fanout"
|
|
18
|
+
HEADERS = "amq.headers"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@unique
|
|
22
|
+
class DeliveryMode(IntEnum):
|
|
23
|
+
|
|
24
|
+
NOT_PERSISTENT = 1
|
|
25
|
+
PERSISTENT = 2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig, kw_only=True)
|
|
29
|
+
class Config(_Config):
|
|
30
|
+
|
|
31
|
+
## shared resource
|
|
32
|
+
# amqp://user:password@host/vhost?heartbeat=60&x=y
|
|
33
|
+
url: str
|
|
34
|
+
queue_name: str
|
|
35
|
+
enable_declare_queue: bool = False
|
|
36
|
+
# broker 重启后队列仍在;消息是否保留看 delivery_mode
|
|
37
|
+
queue_durable: bool = False
|
|
38
|
+
# 仅当前连接可使用,连接断开后队列删除
|
|
39
|
+
queue_exclusive: bool = False
|
|
40
|
+
# 不创建队列,只确认同名队列已存在,不存在则失败
|
|
41
|
+
queue_passive: bool = False
|
|
42
|
+
# 最后一个消费者取消或断开后删除队列
|
|
43
|
+
queue_auto_delete: bool = False
|
|
44
|
+
# 声明时的附加参数,例如 x-message-ttl、x-dead-letter-exchange
|
|
45
|
+
queue_arguments: dict | None = None
|
|
46
|
+
|
|
47
|
+
## producer
|
|
48
|
+
exchange_name: ExchangeName | str = ExchangeName.DEFAULT
|
|
49
|
+
routing_key: str = ""
|
|
50
|
+
delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT
|
|
51
|
+
|
|
52
|
+
## consumer
|
|
53
|
+
prefetch_count: int = 0
|
|
54
|
+
prefetch_size: int = 0
|
|
55
|
+
global_qos: bool = False
|
queue_func/consumer/base.py
CHANGED
|
@@ -7,36 +7,43 @@ import contextlib
|
|
|
7
7
|
import typing as t
|
|
8
8
|
from functools import wraps
|
|
9
9
|
|
|
10
|
-
from queue_func.
|
|
10
|
+
from queue_func.serializer.base import Message
|
|
11
|
+
from queue_func.shared_resource.base import Resource
|
|
11
12
|
from queue_func.pool_executor.base import BasePoolExecutor
|
|
13
|
+
from queue_func.utils.hook import OnceHooks
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class BaseConsumer(t.Generic[
|
|
16
|
+
class BaseConsumer(t.Generic[Message, Resource], metaclass=abc.ABCMeta):
|
|
15
17
|
|
|
16
18
|
__slots__ = (
|
|
17
|
-
"
|
|
19
|
+
"_resource",
|
|
18
20
|
"_coro",
|
|
19
21
|
"_enable_consumer",
|
|
20
22
|
"_pool_executor",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
+
"_consume_timeout",
|
|
24
|
+
"_startup",
|
|
25
|
+
"_shutdown",
|
|
26
|
+
"_stopped",
|
|
23
27
|
"_logger",
|
|
24
28
|
)
|
|
25
29
|
|
|
26
30
|
def __init__(
|
|
27
31
|
self,
|
|
28
|
-
|
|
32
|
+
resource: Resource,
|
|
29
33
|
coro: t.Callable[..., t.Coroutine],
|
|
30
34
|
enable_consumer: bool,
|
|
35
|
+
consume_timeout: float,
|
|
31
36
|
pool_executor_cls: type[BasePoolExecutor],
|
|
32
37
|
pool_maxsize: int):
|
|
33
38
|
|
|
34
|
-
self.
|
|
39
|
+
self._resource = resource
|
|
35
40
|
self._coro = coro
|
|
36
41
|
self._enable_consumer = enable_consumer
|
|
37
42
|
self._pool_executor = self._init_pool_executor(pool_executor_cls, pool_maxsize)
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
43
|
+
self._consume_timeout = consume_timeout
|
|
44
|
+
self._stopped = False
|
|
45
|
+
self._startup = OnceHooks()
|
|
46
|
+
self._shutdown = OnceHooks()
|
|
40
47
|
self._logger = logging.getLogger(__name__)
|
|
41
48
|
|
|
42
49
|
def _init_pool_executor(
|
|
@@ -50,15 +57,15 @@ class BaseConsumer(t.Generic[Packed], metaclass=abc.ABCMeta):
|
|
|
50
57
|
|
|
51
58
|
def _run_until_stopped(
|
|
52
59
|
self,
|
|
53
|
-
|
|
54
|
-
delay: float = 0.
|
|
60
|
+
fn: t.Callable[..., t.Awaitable],
|
|
61
|
+
delay: float = 0.05) -> t.Callable[..., t.Coroutine]:
|
|
55
62
|
|
|
56
|
-
@wraps(
|
|
63
|
+
@wraps(fn)
|
|
57
64
|
async def wrapped(*args, **kwargs) -> None:
|
|
58
65
|
|
|
59
|
-
while not self.
|
|
66
|
+
while not self._stopped:
|
|
60
67
|
try:
|
|
61
|
-
await
|
|
68
|
+
await fn(*args, **kwargs)
|
|
62
69
|
except Exception as e:
|
|
63
70
|
self._logger.exception(e)
|
|
64
71
|
await asyncio.sleep(delay)
|
|
@@ -73,10 +80,10 @@ class BaseConsumer(t.Generic[Packed], metaclass=abc.ABCMeta):
|
|
|
73
80
|
async def consume(self) -> None:
|
|
74
81
|
|
|
75
82
|
if self._enable_consumer:
|
|
76
|
-
self.
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
|
|
83
|
+
self._stopped = False
|
|
84
|
+
self._startup.register(self._run_until_stopped(self._consume))
|
|
85
|
+
self._register_shutdown()
|
|
86
|
+
await self._startup.start()
|
|
80
87
|
|
|
81
88
|
@abc.abstractmethod
|
|
82
89
|
async def _consume(self) -> None: # pragma: no cover
|
|
@@ -85,13 +92,22 @@ class BaseConsumer(t.Generic[Packed], metaclass=abc.ABCMeta):
|
|
|
85
92
|
|
|
86
93
|
async def stop(self) -> None:
|
|
87
94
|
|
|
88
|
-
self.
|
|
95
|
+
await self._shutdown.start(prepare=self._mark_stopped)
|
|
96
|
+
|
|
97
|
+
def _mark_stopped(self) -> None:
|
|
98
|
+
|
|
99
|
+
self._stopped = True
|
|
89
100
|
|
|
90
101
|
async def join(self) -> None:
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
await self._shutdown.wait()
|
|
104
|
+
|
|
105
|
+
async def _join_pool_executor(self):
|
|
95
106
|
|
|
96
107
|
if self._pool_executor is not None:
|
|
97
108
|
await self._pool_executor.join()
|
|
109
|
+
|
|
110
|
+
def _register_shutdown(self) -> None:
|
|
111
|
+
|
|
112
|
+
self._shutdown.register(self._startup.wait)
|
|
113
|
+
self._shutdown.register(self._join_pool_executor)
|
queue_func/consumer/queue.py
CHANGED
|
@@ -3,53 +3,30 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import typing as t
|
|
5
5
|
|
|
6
|
-
from queue_func.
|
|
6
|
+
from queue_func.serializer.base import Message
|
|
7
|
+
from queue_func.shared_resource.queue import Resource
|
|
7
8
|
from queue_func.pool_executor.base import BasePoolExecutor
|
|
8
9
|
from queue_func.consumer.base import BaseConsumer as _BaseConsumer
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class Consumer(_BaseConsumer[
|
|
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
|
|
12
|
+
class Consumer(_BaseConsumer[Message, Resource]):
|
|
37
13
|
|
|
38
14
|
async def _consume(self) -> None:
|
|
39
15
|
|
|
40
|
-
while (not self.
|
|
16
|
+
while (not self._stopped) or (not self._resource.queue.empty()):
|
|
41
17
|
try:
|
|
42
|
-
message:
|
|
43
|
-
self.
|
|
18
|
+
message: Message = await asyncio.wait_for(
|
|
19
|
+
self._resource.queue.get(),
|
|
44
20
|
timeout=self._consume_timeout,
|
|
45
21
|
)
|
|
46
22
|
except asyncio.TimeoutError:
|
|
47
23
|
continue
|
|
48
|
-
coro_args, coro_kwargs = self.
|
|
24
|
+
coro_args, coro_kwargs = self._resource.serializer.unpack(message)
|
|
49
25
|
await self._dispatch(coro_args, coro_kwargs)
|
|
50
|
-
self.
|
|
26
|
+
self._resource.queue.task_done()
|
|
51
27
|
|
|
52
|
-
|
|
28
|
+
def _register_shutdown(self) -> None:
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
|
|
30
|
+
self._shutdown.register(self._startup.wait)
|
|
31
|
+
self._shutdown.register(self._resource.queue.join)
|
|
32
|
+
self._shutdown.register(self._join_pool_executor)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from queue_func.consumer.base import BaseConsumer as _BaseConsumer
|
|
7
|
+
from queue_func.pool_executor.base import BasePoolExecutor
|
|
8
|
+
from queue_func.shared_resource.rabbit_aio_pika import Resource
|
|
9
|
+
|
|
10
|
+
if t.TYPE_CHECKING:
|
|
11
|
+
from aio_pika.abc import AbstractIncomingMessage, AbstractQueue
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Consumer(_BaseConsumer[bytes, Resource]):
|
|
15
|
+
|
|
16
|
+
__slots__ = (
|
|
17
|
+
"_prefetch_count",
|
|
18
|
+
"_prefetch_size",
|
|
19
|
+
"_global_qos",
|
|
20
|
+
"_channel",
|
|
21
|
+
"_queue",
|
|
22
|
+
"_iterator",
|
|
23
|
+
"_setup_lock",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
resource: Resource,
|
|
29
|
+
prefetch_count: int,
|
|
30
|
+
prefetch_size: int,
|
|
31
|
+
global_qos: bool,
|
|
32
|
+
coro: t.Callable[..., t.Coroutine],
|
|
33
|
+
enable_consumer: bool,
|
|
34
|
+
consume_timeout: float,
|
|
35
|
+
pool_executor_cls: type[BasePoolExecutor],
|
|
36
|
+
pool_maxsize: int):
|
|
37
|
+
|
|
38
|
+
super().__init__(
|
|
39
|
+
resource=resource,
|
|
40
|
+
coro=coro,
|
|
41
|
+
enable_consumer=enable_consumer,
|
|
42
|
+
consume_timeout=consume_timeout,
|
|
43
|
+
pool_executor_cls=pool_executor_cls,
|
|
44
|
+
pool_maxsize=pool_maxsize,
|
|
45
|
+
)
|
|
46
|
+
self._prefetch_count = prefetch_count
|
|
47
|
+
self._prefetch_size = prefetch_size
|
|
48
|
+
self._global_qos = global_qos
|
|
49
|
+
self._channel: "AbstractChannel | None" = None
|
|
50
|
+
self._queue: "AbstractQueue | None" = None
|
|
51
|
+
self._setup_lock = asyncio.Lock()
|
|
52
|
+
|
|
53
|
+
async def _consume(self) -> None:
|
|
54
|
+
|
|
55
|
+
await self._ensure_queue()
|
|
56
|
+
while not self._stopped:
|
|
57
|
+
async with self._queue.iterator(timeout=self._consume_timeout) as queue_iter:
|
|
58
|
+
try:
|
|
59
|
+
message: "AbstractIncomingMessage"
|
|
60
|
+
async for message in queue_iter:
|
|
61
|
+
body: bytes = message.body
|
|
62
|
+
coro_args, coro_kwargs = self._resource.serializer.unpack(body)
|
|
63
|
+
await self._dispatch(coro_args, coro_kwargs)
|
|
64
|
+
await message.ack()
|
|
65
|
+
except asyncio.TimeoutError:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
async def _ensure_queue(self) -> None:
|
|
69
|
+
|
|
70
|
+
if self._queue is not None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
async with self._setup_lock:
|
|
74
|
+
if self._queue is not None:
|
|
75
|
+
return None
|
|
76
|
+
channel = await self._resource.get_channel()
|
|
77
|
+
await channel.set_qos(
|
|
78
|
+
prefetch_count=self._prefetch_count,
|
|
79
|
+
prefetch_size=self._prefetch_size,
|
|
80
|
+
global_=self._global_qos,
|
|
81
|
+
)
|
|
82
|
+
queue = await self._resource.declare_queue(channel)
|
|
83
|
+
self._channel, self._queue = channel, queue
|
|
84
|
+
|
|
85
|
+
async def _release(self) -> None:
|
|
86
|
+
|
|
87
|
+
channel = self._channel
|
|
88
|
+
if channel is None:
|
|
89
|
+
return None
|
|
90
|
+
self._channel = None
|
|
91
|
+
self._queue = None
|
|
92
|
+
await self._resource.close_channel(channel)
|
|
93
|
+
await self._resource.try_close_connection()
|
|
94
|
+
|
|
95
|
+
def _register_shutdown(self) -> None:
|
|
96
|
+
|
|
97
|
+
self._shutdown.register(self._startup.wait)
|
|
98
|
+
self._shutdown.register(self._release)
|
|
99
|
+
self._shutdown.register(self._join_pool_executor)
|
queue_func/errors.py
ADDED
queue_func/factory/backend.py
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
"""Import builder modules so they register with :func:`register_backend`."""
|
|
4
4
|
|
|
5
5
|
from queue_func.factory.builders import queue as queue
|
|
6
|
+
from queue_func.factory.builders import rabbit_aio_pika as rabbit_aio_pika
|
|
6
7
|
|
|
7
8
|
__all__ = (
|
|
8
9
|
"queue",
|
|
10
|
+
"rabbit_aio_pika",
|
|
9
11
|
)
|
|
@@ -0,0 +1,26 @@
|
|
|
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.RABBIT_AIO_PIKA)
|
|
13
|
+
def build_rabbit_aio_pika_broker(
|
|
14
|
+
coro: t.Callable[..., t.Coroutine],
|
|
15
|
+
**kwargs: t.Any) -> "BaseBroker[t.Any, t.Any]":
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from queue_func.broker.rabbit_aio_pika import Broker
|
|
19
|
+
from queue_func.config.rabbit_aio_pika import Config
|
|
20
|
+
except ImportError as exc:
|
|
21
|
+
raise ImportError(
|
|
22
|
+
"Backend.RABBIT_AIO_PIKA requires aio-pika. "
|
|
23
|
+
"Install it with the rabbit extra.",
|
|
24
|
+
) from exc
|
|
25
|
+
|
|
26
|
+
return Broker(Config(coro=coro, **kwargs))
|