queue-func 0.1.2__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/broker/base.py +4 -24
- queue_func/broker/queue.py +14 -24
- queue_func/broker/rabbit_aio_pika.py +47 -0
- queue_func/config/general.py +6 -6
- queue_func/config/queue.py +1 -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/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 +30 -0
- queue_func/producer/base.py +35 -10
- queue_func/producer/queue.py +7 -18
- queue_func/producer/rabbit_aio_pika.py +83 -0
- queue_func/serializer/base.py +1 -1
- queue_func/serializer/orjson_ser.py +2 -2
- queue_func/serializer/raw.py +2 -2
- queue_func/shared_resource/__init__.py +1 -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-0.1.2.dist-info/METADATA +0 -183
- queue_func-0.1.2.dist-info/RECORD +0 -37
- {queue_func-0.1.2.dist-info → queue_func-0.2.1.dist-info}/WHEEL +0 -0
- {queue_func-0.1.2.dist-info → queue_func-0.2.1.dist-info}/licenses/LICENSE +0 -0
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.serializer.base import
|
|
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
|
|
@@ -22,33 +23,12 @@ class BaseBroker(t.Generic[ConfigType, Message], metaclass=abc.ABCMeta):
|
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
25
|
__slots__ = (
|
|
25
|
-
"_config",
|
|
26
|
-
"_serializer",
|
|
27
26
|
"_producer",
|
|
28
27
|
"_consumer",
|
|
29
28
|
)
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self._config = config
|
|
34
|
-
self._serializer: type[BaseSerializer[Message]] = self._config.serializer_cls
|
|
35
|
-
self._producer = self._create_producer()
|
|
36
|
-
self._consumer = self._create_consumer()
|
|
37
|
-
|
|
38
|
-
@abc.abstractmethod
|
|
39
|
-
def _create_producer(self) -> "BaseProducer[Message]":
|
|
40
|
-
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
@abc.abstractmethod
|
|
44
|
-
def _create_consumer(self) -> "BaseConsumer[Message]":
|
|
45
|
-
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
@property
|
|
49
|
-
def serializer(self) -> type[BaseSerializer[Message]]:
|
|
50
|
-
|
|
51
|
-
return self._serializer
|
|
30
|
+
_producer: "BaseProducer"
|
|
31
|
+
_consumer: "BaseConsumer"
|
|
52
32
|
|
|
53
33
|
async def publish(
|
|
54
34
|
self,
|
queue_func/broker/queue.py
CHANGED
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import typing as t
|
|
5
5
|
|
|
6
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
|
|
@@ -15,30 +16,19 @@ if t.TYPE_CHECKING:
|
|
|
15
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
|
-
serializer=self._serializer,
|
|
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
|
@@ -11,18 +11,18 @@ from queue_func.pool_executor.base import BasePoolExecutor
|
|
|
11
11
|
from queue_func.pool_executor.general import PoolExecutor
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@pyd_dataclass(frozen=True, config=ExtraIgnoreConfig)
|
|
14
|
+
@pyd_dataclass(frozen=True, kw_only=True, config=ExtraIgnoreConfig)
|
|
15
15
|
class Config:
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
## shared resource
|
|
18
|
+
serializer_cls: type[BaseSerializer] = Serializer
|
|
19
|
+
|
|
20
|
+
## consumer
|
|
18
21
|
coro: t.Callable[[...], t.Coroutine]
|
|
19
22
|
enable_consumer: bool = True
|
|
20
23
|
consume_timeout: float = 2.0
|
|
21
24
|
consumer_pool_executor_cls: type[BasePoolExecutor] = PoolExecutor
|
|
22
25
|
consumer_pool_maxsize: int = 200
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
serializer_cls: type[BaseSerializer] = Serializer
|
|
26
|
-
|
|
27
|
-
# producer
|
|
27
|
+
## producer
|
|
28
28
|
publish_timeout: float | None = None
|
queue_func/config/queue.py
CHANGED
|
@@ -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.serializer.base import
|
|
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[Message], metaclass=abc.ABCMeta):
|
|
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[Message], 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[Message], 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[Message], 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.serializer.base import
|
|
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[Message]):
|
|
12
|
-
|
|
13
|
-
__slots__ = (
|
|
14
|
-
"_queue",
|
|
15
|
-
"_consume_timeout",
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
queue: asyncio.Queue[Message],
|
|
21
|
-
consume_timeout: float,
|
|
22
|
-
serializer: type[BaseSerializer[Message]],
|
|
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
|
-
serializer,
|
|
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
|
-
|
|
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/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))
|
queue_func/factory/deco.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import typing as t
|
|
4
4
|
|
|
5
|
+
from queue_func.config.rabbit_aio_pika import DeliveryMode, ExchangeName
|
|
5
6
|
from queue_func.factory import builders as _builders # noqa: F401 — register backends
|
|
6
7
|
from queue_func.factory.backend import Backend
|
|
7
8
|
from queue_func.factory.registry import build_broker
|
|
@@ -84,6 +85,35 @@ def queue(
|
|
|
84
85
|
...
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
@t.overload
|
|
89
|
+
def queue(
|
|
90
|
+
backend: t.Literal[Backend.RABBIT_AIO_PIKA],
|
|
91
|
+
*,
|
|
92
|
+
url: str,
|
|
93
|
+
queue_name: str,
|
|
94
|
+
enable_declare_queue: bool = ...,
|
|
95
|
+
queue_durable: bool = ...,
|
|
96
|
+
queue_exclusive: bool = ...,
|
|
97
|
+
queue_passive: bool = ...,
|
|
98
|
+
queue_auto_delete: bool = ...,
|
|
99
|
+
queue_arguments: dict[str, t.Any] | None = ...,
|
|
100
|
+
serializer_cls: type["BaseSerializer"] = ...,
|
|
101
|
+
enable_consumer: bool = ...,
|
|
102
|
+
consume_timeout: float = ...,
|
|
103
|
+
consumer_pool_executor_cls: type["BasePoolExecutor"] = ...,
|
|
104
|
+
consumer_pool_maxsize: int = ...,
|
|
105
|
+
prefetch_count: int = ...,
|
|
106
|
+
prefetch_size: int = ...,
|
|
107
|
+
global_qos: bool = ...,
|
|
108
|
+
exchange_name: ExchangeName | str = ...,
|
|
109
|
+
routing_key: str = ...,
|
|
110
|
+
delivery_mode: DeliveryMode = ...,
|
|
111
|
+
publish_timeout: float | None = ...,
|
|
112
|
+
) -> t.Callable[[t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]], QueueFunc[_P, _R]]:
|
|
113
|
+
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
|
|
87
117
|
def queue(
|
|
88
118
|
backend: Backend,
|
|
89
119
|
**config_kwargs: t.Any) -> t.Callable[[t.Callable[_P, t.Coroutine[t.Any, t.Any, _R]]], QueueFunc[_P, _R]]:
|
queue_func/producer/base.py
CHANGED
|
@@ -3,23 +3,33 @@
|
|
|
3
3
|
import abc
|
|
4
4
|
import typing as t
|
|
5
5
|
|
|
6
|
-
from queue_func.
|
|
6
|
+
from queue_func.shared_resource.base import Resource
|
|
7
|
+
from queue_func.serializer.base import Message
|
|
8
|
+
from queue_func.utils.hook import OnceHooks
|
|
9
|
+
from queue_func.utils.waiter import IdleWaiter
|
|
7
10
|
|
|
8
11
|
|
|
9
|
-
class BaseProducer(t.Generic[Message], metaclass=abc.ABCMeta):
|
|
12
|
+
class BaseProducer(t.Generic[Message, Resource], metaclass=abc.ABCMeta):
|
|
10
13
|
|
|
11
14
|
__slots__ = (
|
|
12
|
-
"
|
|
15
|
+
"_resource",
|
|
13
16
|
"_publish_timeout",
|
|
17
|
+
"_stopped",
|
|
18
|
+
"_shutdown",
|
|
19
|
+
"_pub_waiter",
|
|
14
20
|
)
|
|
15
21
|
|
|
16
22
|
def __init__(
|
|
17
23
|
self,
|
|
18
|
-
|
|
24
|
+
resource: Resource,
|
|
19
25
|
publish_timeout: float | None):
|
|
20
26
|
|
|
21
|
-
self.
|
|
27
|
+
self._resource = resource
|
|
22
28
|
self._publish_timeout = publish_timeout
|
|
29
|
+
self._stopped = False
|
|
30
|
+
self._shutdown = OnceHooks()
|
|
31
|
+
self._pub_waiter = IdleWaiter()
|
|
32
|
+
self._register_shutdown()
|
|
23
33
|
|
|
24
34
|
async def publish(
|
|
25
35
|
self,
|
|
@@ -28,17 +38,32 @@ class BaseProducer(t.Generic[Message], metaclass=abc.ABCMeta):
|
|
|
28
38
|
*,
|
|
29
39
|
timeout: float | None = None) -> None:
|
|
30
40
|
|
|
41
|
+
|
|
42
|
+
if self._stopped:
|
|
43
|
+
raise RuntimeError("publish called on already stopped")
|
|
44
|
+
|
|
31
45
|
if timeout is None:
|
|
32
46
|
timeout = self._publish_timeout
|
|
33
|
-
packed = self.
|
|
34
|
-
|
|
47
|
+
packed = self._resource.serializer.pack(coro_args, coro_kwargs)
|
|
48
|
+
async with self._pub_waiter.hold():
|
|
49
|
+
await self._publish(packed, timeout=timeout)
|
|
35
50
|
|
|
36
51
|
@abc.abstractmethod
|
|
37
|
-
async def _publish(self,
|
|
52
|
+
async def _publish(self, msg: Message, *, timeout: float | None) -> None:
|
|
38
53
|
pass
|
|
39
54
|
|
|
40
55
|
async def stop(self) -> None:
|
|
41
|
-
|
|
56
|
+
|
|
57
|
+
await self._shutdown.start(prepare=self._mark_stopped)
|
|
58
|
+
|
|
59
|
+
def _mark_stopped(self) -> None:
|
|
60
|
+
|
|
61
|
+
self._stopped = True
|
|
42
62
|
|
|
43
63
|
async def join(self) -> None:
|
|
44
|
-
|
|
64
|
+
|
|
65
|
+
await self._shutdown.wait()
|
|
66
|
+
|
|
67
|
+
def _register_shutdown(self):
|
|
68
|
+
|
|
69
|
+
self._shutdown.register(self._pub_waiter.wait)
|