init project

master
JimZhang 1 year ago
commit bcf32df44f

160
.gitignore vendored

@ -0,0 +1,160 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 JimZhang <zzl22100048@gmail.com> (https://www.loom.run)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,65 @@
# Busrt Worker
> `BUS/RT`是一个快速、灵活且非常易于使用的框架它使用Rust/Tokio编写受到NATS、ZeroMQ和Nanomsg的启发。
Busrt Worker是一个基于busrt消息中间件的异步框架它对Python库进行了封装使其更易于使用。
## 特点
- 异步Busrt Worker基于python asyncio异步引擎可以轻松处理高并发请求。
- 易用性Busrt Worker对原生的busrt python客户端进行了封装使用装饰器即可轻松创建rpc服务。
- 高性能BUS/RT 使用Rust/Tokio编写具有出色的性能和可靠性。
## 用法
要使用Busrt Worker请按照以下步骤操作
1. 安装Busrt Worker`pip install busrt-worker`
2. 导入Busrt Worker在您的Python项目中导入Busrt Worker。
3. 创建Busrt Worker App创建一个App对象并注册连接信息。
4. 处理消息:使用装饰器指明消息处理方式。
以下是一个示例代码片段演示如何使用Busrt Worker
```python
import asyncio
from loguru import logger
from busrtworker import App, ConnectionInfo
# 创建 App对象
app = App()
# 连接信息
api_ci = ConnectionInfo('api', 'localhost:9800', 'busrt.worker.test', static=True, topic='test/#')
caller_ci = ConnectionInfo('caller', 'localhost:9800', 'busrt.worker.test', static=True)
# 注册连接
api = app.registry(api_ci)
app.registry(caller_ci)
# rpc调用
@api.on_call()
def add(a, b):
return a + b
# 主题订阅
@api.subscribe('test/:name')
def print_name(name: str):
logger.info(f'{name} pub message')
# 钩子函数注册
@app.run_on_startup
async def test(server):
async def call():
await asyncio.sleep(1)
logger.info(f'call remote add result {(await app.caller.add(api_ci.final_name,a=1, b=2))}')
await app.caller.send('test/i_am_caller')
asyncio.create_task(call())
# 启动程序
app.run()
```
## License
The MIT License.

@ -0,0 +1,2 @@
from .app import App,ConnectionInfo

@ -0,0 +1,277 @@
import abc
import asyncio
import dataclasses
import functools
import signal
from collections import UserList
from enum import Enum, auto
from typing import Callable, Coroutine, Dict, List, Optional
from loguru import logger
from busrtworker.boostrap import RpcBoot
from busrtworker.busrt import OP_PUBLISH, Client, Frame, Rpc, serialize
from busrtworker.scheduler import ScheduledTaskRunner
from busrtworker.tree import RadixTree
@dataclasses.dataclass
class ConnectionInfo:
name: str = dataclasses.field()
uri: str = dataclasses.field()
client_prefix: str = dataclasses.field()
static: bool = dataclasses.field()
topic: str | None = dataclasses.field(default=None)
init_rpc: bool = dataclasses.field(default=True)
bus: Client = dataclasses.field(default=None, init=False)
rpc: Rpc = dataclasses.field(default=None, init=False)
final_name: str = dataclasses.field(default=None, init=False)
def __getattr__(self, item):
if not self.init_rpc:
raise ValueError('must be init rpc client could call')
return getattr(self.rpc, item)
async def send(self, topic, data=None, decode=True):
bus: Client = self.bus
await bus.send(topic, Frame(serialize(data) if decode else data, tp=OP_PUBLISH))
class Router:
table: dict = {}
tree: RadixTree = RadixTree()
def insert(self, path, handler, dynamic=False):
if not dynamic:
if path in self.table:
raise ValueError(f'conflict route {path}')
self.table[path] = handler
else:
self.tree.insert(path, handler, ['RPC'])
def get(self, path):
if path in self.table:
return True, self.table[path], {}
return self.tree.get(path, 'RPC')
class ServiceEntrypoint:
def __init__(self, connection: ConnectionInfo, app: 'App'):
self.name = connection.name
self.app = app
if self.name not in self.app.callers:
self.app.callers[self.name] = {}
if self.name not in self.app.subscribes:
self.app.subscribes[self.name] = Router()
def on_call(self, method=None, auto_decode=True, raw=False):
def _warp(f):
target = method or (f.func.__name__ if isinstance(f, functools.partial) else f.__name__)
self.app.callers[self.name][target] = (f, auto_decode, asyncio.iscoroutinefunction(f), raw)
return f
return _warp
def subscribe(self, topic, auto_decode=True, raw=False):
assert isinstance(topic, str), 'topic must be str or callable'
def _warp(f):
self.app.subscribes[self.name].insert(topic, (f, auto_decode, asyncio.iscoroutinefunction(f), raw),
'/:' in topic)
return f
return _warp
class Freezable(metaclass=abc.ABCMeta):
def __init__(self):
self._frozen = False
@property
def frozen(self) -> bool:
return self._frozen
async def freeze(self):
self._frozen = True
class Signal(UserList, asyncio.Event):
"""
Coroutine-based signal implementation tha behaves as an `asyncio.Event`.
To connect a callback to a signal, use any list method.
Signals are fired using the send() coroutine, which takes named
arguments.
"""
def __init__(self, owner: Freezable) -> None:
UserList.__init__(self)
asyncio.Event.__init__(self)
self._owner = owner
self.frozen = False
def __repr__(self):
return "<Signal owner={}, frozen={}, {!r}>".format(
self._owner, self.frozen, list(self)
)
async def send(self, *args, **kwargs):
"""
Sends data to all registered receivers.
"""
if self.frozen:
raise RuntimeError("Cannot send on frozen signal.")
for receiver in self:
await receiver(*args, **kwargs)
self.frozen = True
await self._owner.freeze()
self.set()
async def on_frame_default(app, frame):
logger.opt(lazy=True).debug('{x}',
x=lambda: f"default print 'Frame:', {hex(frame.type)}, frame.sender, frame.topic, frame.payload")
async def on_call_default(app, event):
logger.opt(lazy=True).debug('{x}',
x=lambda: f"default print 'Rpc:', {event.frame.sender}, {event.method}, {event.get_payload()}")
def entrypoint(f):
@functools.wraps(f)
def _(*args, **kwargs):
try:
loop = asyncio.get_event_loop()
return loop.run_until_complete(f(*args, **kwargs))
except KeyboardInterrupt:
pass
return _
class AutoNameEnum(str, Enum):
def _generate_next_value_( # type: ignore
name: str, start: int, count: int, last_values: List[str]
) -> str:
return name.lower()
class Options(AutoNameEnum):
MAX_CONCURRENCY = auto()
class DefaultValues:
RUN_EVERY_MAX_CONCURRENCY = 1
class App(Freezable):
callers: dict[str, callable] = {}
subscribes: dict[str, Router] = {}
connections: Dict[str, ConnectionInfo] = {}
closeable = []
on_frame_default: callable = on_frame_default
on_call_default: callable = on_call_default
task_runners = []
def __init__(self):
Freezable.__init__(self)
self.boot = RpcBoot()
self._on_startup: Signal = Signal(self)
self._on_shutdown: Signal = Signal(self)
self._on_startup.append(self.boot.startup)
self._on_shutdown.append(self.boot.shutdown)
signal.signal(signal.SIGINT, self.shutdown)
signal.signal(signal.SIGTERM, self.shutdown)
def registry(self, connection: ConnectionInfo):
if self.frozen:
raise RuntimeError(
"You shouldn't change the state of a started application"
)
self.connections[connection.name] = connection
return ServiceEntrypoint(connection, self)
def set_on_frame_default(self, on_frame: callable):
self.on_frame_default = on_frame
def set_on_call_default(self, on_call: callable):
self.on_call_default = on_call
@entrypoint
async def run(self):
if self.frozen:
raise RuntimeError(
"You shouldn't change the state of a started application"
)
logger.debug({"event": "Booting App..."})
await self.startup()
await self._on_shutdown.wait()
async def startup(self):
"""
Causes on_startup signal
Should be called in the event loop along with the request handler.
"""
await self._on_startup.send(self)
def shutdown(self, *args) -> asyncio.Future:
"""
Schedules an on_startup signal
Is called automatically when the application receives
a SIGINT or SIGTERM
"""
logger.debug('do shutdown')
return asyncio.ensure_future(self._on_shutdown.send(self))
def run_on_startup(self, coro: Callable[["App"], Coroutine]) -> None:
"""
Registers a coroutine to be awaited for during app startup
"""
self._on_startup.append(coro)
def run_on_shutdown(self, coro: Callable[["App"], Coroutine]) -> None:
"""
Registers a coroutine to be awaited for during app shutdown
"""
self._on_shutdown.append(coro)
def __getattr__(self, name):
return self.connections[name]
def run_every(self, seconds: int, options: Optional[Dict] = None):
"""
Registers a coroutine to be called with a given interval
"""
if options is None:
options = {}
max_concurrency = options.get(
Options.MAX_CONCURRENCY, DefaultValues.RUN_EVERY_MAX_CONCURRENCY
)
def wrapper(task: Callable[..., Coroutine]):
runner = ScheduledTaskRunner(
seconds=seconds,
task=task,
max_concurrency=max_concurrency,
)
self._on_startup.append(runner.start)
self._on_shutdown.append(runner.stop)
self.task_runners.append(runner)
return task
return wrapper
def rpc_running(self, name):
if connection := self.connections.get(name, None):
if rpc := connection.rpc:
return rpc.is_connected()
return False

@ -0,0 +1,95 @@
import asyncio
import functools
import os
import traceback
from typing import TYPE_CHECKING
from .busrt import Client, Rpc, RpcException, deserialize, on_frame_default, serialize
if TYPE_CHECKING: # pragma: no cover
from .app import App # noqa: F401
class RpcBoot:
async def startup(self, app: 'App'):
for i, (name, connection) in enumerate(app.connections.items()):
uri = connection.uri
static = connection.static
init_rpc = connection.init_rpc
prefix = connection.client_prefix
topic = connection.topic
connection.final_name = f'{prefix}.{name}' if static else f'{prefix}.{os.urandom(4).hex()}'
bus = Client(uri, connection.final_name)
connection.bus = bus
app.closeable.append(bus.disconnect)
await bus.connect()
if topic:
await bus.subscribe(topic)
async def on_frame(frame, router):
success, route, params = router.get(frame.topic)
if not success:
return await app.on_frame_default(frame)
else:
func, auto_decode, is_async, raw = route
if auto_decode:
payload = deserialize(frame.payload)
if raw:
payload['frame'] = frame
if is_async:
return await func(**payload, **params)
return func(**payload, **params)
else:
if is_async:
return await func(frame, **params)
return func(frame, **params)
on_frame_func = functools.partial(on_frame, router=app.subscribes.get(name, None))
else:
on_frame_func = on_frame_default
if init_rpc:
async def on_call(event, caller):
try:
method = event.method.decode('utf-8')
caller_tuple = caller.get(method, None)
if not caller_tuple:
return serialize(await app.on_call_default(event))
call, auto_decode, is_async, raw = caller_tuple
if auto_decode:
payload = deserialize(event.get_payload())
if raw:
payload['event'] = event
if is_async:
return serialize(await call(**payload))
return serialize(call(**payload))
else:
if is_async:
return serialize(await call(event))
return serialize(call(event))
except Exception as e:
traceback.print_exc()
raise RpcException(str(e), 11)
rpc_client = Rpc(bus)
connection.rpc = rpc_client
if topic:
rpc_client.on_frame = functools.partial(on_frame_func, router=app.subscribes.get(name, None))
rpc_client.on_call = functools.partial(on_call, caller=app.callers.get(name, None))
elif topic:
bus.on_frame = functools.partial(on_frame_func, router=app.subscribes.get(name, None))
async def shutdown(self, app: 'App'):
gathers = []
for func in app.closeable:
if asyncio.iscoroutinefunction(func):
gathers.append(func())
else:
try:
func()
except:
traceback.print_exc()
if gathers:
await asyncio.gather(*gathers, return_exceptions=True)

@ -0,0 +1,3 @@
from .busrt_client import * # noqa: F403
from .busrt_rpc import * # noqa: F403
from .msgutils import * # noqa: F403

@ -0,0 +1,274 @@
import asyncio
from loguru import logger
GREETINGS = 0xEB
PROTOCOL_VERSION = 1
OP_NOP = 0
OP_PUBLISH = 1
OP_SUBSCRIBE = 2
OP_UNSUBSCRIBE = 3
OP_MESSAGE = 0x12
OP_BROADCAST = 0x13
OP_ACK = 0xFE
RESPONSE_OK = 0x01
ERR_CLIENT_NOT_REGISTERED = 0x71
ERR_DATA = 0x72
ERR_IO = 0x73
ERR_OTHER = 0x74
ERR_NOT_SUPPORTED = 0x75
ERR_BUSY = 0x76
ERR_NOT_DELIVERED = 0x77
ERR_TIMEOUT = 0x78
PING_FRAME = b'\x00' * 9
async def on_frame_default(frame):
pass
class Client:
def __init__(self, path, name):
self.path = path
self.writer = None
self.reader_fut = None
self.pinger_fut = None
self.buf_size = 8192
self.name = name
self.frame_id = 0
self.ping_interval = 1
self.on_frame = on_frame_default
self.socket_lock = asyncio.Lock()
self.mgmt_lock = asyncio.Lock()
self.connected = False
self.frames = {}
self.timeout = 1
async def connect(self):
async with self.mgmt_lock:
if self.path.endswith('.sock') or self.path.endswith(
'.socket') or self.path.endswith(
'.ipc') or self.path.startswith('/'):
if hasattr(asyncio, 'open_unix_connection'):
reader, writer = await asyncio.open_unix_connection(
self.path, limit=self.buf_size)
else:
raise ValueError('only support unix like system')
else:
host, port = self.path.rsplit(':', maxsplit=2)
reader, writer = await asyncio.open_connection(
host, int(port), limit=self.buf_size)
buf = await asyncio.wait_for(self.readexactly(reader, 3),
timeout=self.timeout)
if buf[0] != GREETINGS:
raise RuntimeError('Unsupported protocol')
if int.from_bytes(buf[1:3], 'little') != PROTOCOL_VERSION:
raise RuntimeError('Unsupported protocol version')
writer.write(buf)
await asyncio.wait_for(writer.drain(), timeout=self.timeout)
buf = await asyncio.wait_for(self.readexactly(reader, 1),
timeout=self.timeout)
if buf[0] != RESPONSE_OK:
raise RuntimeError(f'Server response: {hex(buf[0])}')
name = self.name.encode()
writer.write(len(name).to_bytes(2, 'little') + name)
await asyncio.wait_for(writer.drain(), timeout=self.timeout)
buf = await asyncio.wait_for(self.readexactly(reader, 1),
timeout=self.timeout)
if buf[0] != RESPONSE_OK:
raise RuntimeError(f'Server response: {hex(buf[0])}')
self.writer = writer
self.connected = True
self.pinger_fut = asyncio.ensure_future(self._t_pinger())
self.reader_fut = asyncio.ensure_future(self._t_reader(reader))
async def handle_daemon_exception(self, e):
async with self.mgmt_lock:
if self.connected:
await self._disconnect()
import traceback
logger.error(traceback.format_exc())
async def _t_pinger(self):
try:
while True:
await asyncio.sleep(self.ping_interval)
async with self.socket_lock:
self.writer.write(PING_FRAME)
await asyncio.wait_for(self.writer.drain(),
timeout=self.timeout)
except Exception as e:
asyncio.ensure_future(self.handle_daemon_exception(e))
async def _t_reader(self, reader):
try:
while True:
buf = await self.readexactly(reader, 6)
if buf[0] == OP_NOP:
continue
elif buf[0] == OP_ACK:
op_id = int.from_bytes(buf[1:5], 'little')
try:
o = self.frames.pop(op_id)
o.result = buf[5]
o.completed.set()
except KeyError:
logger.warning(f'orphaned BUS/RT frame ack {op_id}')
else:
async def read_frame(tp, data_len):
frame = Frame()
frame.type = tp
sender = await reader.readuntil(b'\x00')
data_len -= len(sender)
frame.sender = sender[:-1].decode()
if buf[0] == OP_PUBLISH:
topic = await reader.readuntil(b'\x00')
data_len -= len(topic)
frame.topic = topic[:-1].decode()
else:
frame.topic = None
data = b''
while len(data) < data_len:
buf_size = data_len - len(data)
len(data)
try:
data += await reader.readexactly(
buf_size if buf_size < self.buf_size else
self.buf_size)
except asyncio.IncompleteReadError:
pass
frame.payload = data
return frame
try:
data_len = int.from_bytes(buf[1:5], 'little')
frame = await read_frame(buf[0], data_len)
except Exception as e:
logger.error(f'Invalid frame from the server: {e}')
raise
asyncio.ensure_future(self.on_frame(frame))
except Exception as e:
asyncio.ensure_future(self.handle_daemon_exception(e))
async def readexactly(self, reader, data_len):
data = b''
while len(data) < data_len:
buf_size = data_len - len(data)
try:
chunk = await reader.readexactly(
buf_size if buf_size < self.buf_size else self.buf_size)
data += chunk
except asyncio.IncompleteReadError:
await asyncio.sleep(0.01)
return data
async def disconnect(self):
async with self.mgmt_lock:
await self._disconnect()
async def _disconnect(self):
self.connected = False
self.writer.close()
if self.reader_fut is not None:
self.reader_fut.cancel()
if self.pinger_fut is not None:
self.pinger_fut.cancel()
async def send(self, target=None, frame=None):
try:
async with self.socket_lock:
self.frame_id += 1
if self.frame_id > 0xffff_ffff:
self.frame_id = 1
frame_id = self.frame_id
o = ClientFrame(frame.qos)
if frame.qos & 0b1 != 0:
self.frames[frame_id] = o
flags = frame.type | frame.qos << 6
if frame.type == OP_SUBSCRIBE or frame.type == OP_UNSUBSCRIBE:
topics = frame.topic if isinstance(frame.topic,
list) else [frame.topic]
payload = b'\x00'.join(t.encode() for t in topics)
self.writer.write(
frame_id.to_bytes(4, 'little') +
flags.to_bytes(1, 'little') +
len(payload).to_bytes(4, 'little') + payload)
else:
frame_len = len(target) + len(frame.payload) + 1
if frame.header is not None:
frame_len += len(frame.header)
if frame_len > 0xffff_ffff:
raise ValueError('frame too large')
self.writer.write(
frame_id.to_bytes(4, 'little') +
flags.to_bytes(1, 'little') +
frame_len.to_bytes(4, 'little') + target.encode() +
b'\x00' +
(frame.header if frame.header is not None else b''))
self.writer.write(frame.payload.encode(
) if isinstance(frame.payload, str) else frame.payload)
await self.writer.drain()
return o
except:
try:
del self.frames[frame_id]
except KeyError:
pass
raise
def subscribe(self, topics):
frame = Frame(tp=OP_SUBSCRIBE)
frame.topic = topics
return self.send(None, frame)
def unsubscribe(self, topics):
frame = Frame(tp=OP_UNSUBSCRIBE)
frame.topic = topics
return self.send(None, frame)
def is_connected(self):
return self.connected
class ClientFrame:
def __init__(self, qos):
self.qos = qos
self.result = 0
if qos & 0b1 != 0:
self.completed = asyncio.Event()
else:
self.completed = None
def is_completed(self):
if self.qos & 0b1 != 0:
return self.completed.is_set()
else:
return True
async def wait_completed(self, timeout=None):
if self.qos & 0b1 == 0:
return RESPONSE_OK
elif timeout:
await asyncio.wait_for(self.completed.wait(), timeout=timeout)
else:
await self.completed.wait()
return self.result
class Frame:
def __init__(self, payload=None, tp=OP_MESSAGE, qos=0):
self.payload = payload
# used for zero-copy
self.header = None
self.type = tp
self.qos = qos
__all__ = ["Client","Frame","OP_PUBLISH","OP_MESSAGE","OP_BROADCAST","ERR_IO","ERR_TIMEOUT","RESPONSE_OK","on_frame_default"]

@ -0,0 +1,266 @@
import asyncio
import functools
from loguru import logger
from .busrt_client import (
ERR_IO,
ERR_TIMEOUT,
OP_MESSAGE,
RESPONSE_OK,
on_frame_default,
)
from .msgutils import deserialize, serialize
RPC_NOTIFICATION_HEADER = b'\x00'
RPC_REQUEST_HEADER = b'\x01'
RPC_REPLY_HEADER = b'\x11'
RPC_ERROR_REPLY_HEADER = b'\x12'
RPC_NOTIFICATION = 0x00
RPC_REQUEST = 0x01
RPC_REPLY = 0x11
RPC_ERROR = 0x12
RPC_ERROR_CODE_PARSE = -32700
RPC_ERROR_CODE_INVALID_REQUEST = -32600
RPC_ERROR_CODE_METHOD_NOT_FOUND = -32601
RPC_ERROR_CODE_INVALID_METHOD_PARAMS = -32602
RPC_ERROR_CODE_INTERNAL = -32603
async def on_call_default(event):
raise RpcException('RPC Engine not initialized',
RPC_ERROR_CODE_METHOD_NOT_FOUND)
async def on_notification_default(event):
pass
def format_rpc_e_msg(e):
if isinstance(e, RpcException):
return e.rpc_error_payload
else:
return str(e)
class RpcException(Exception):
def __init__(self, msg: str | bytes = '', code=RPC_ERROR_CODE_INTERNAL):
self.rpc_error_code = code
self.rpc_error_payload = msg
super().__init__(msg if isinstance(msg, str) else msg.decode())
def __str__(self):
return super().__str__() + f' (code: {self.rpc_error_code})'
class Rpc:
def __init__(self, client):
self.client = client
self.client.on_frame = self._handle_frame
self.call_id = 0
self.call_lock = asyncio.Lock()
self.calls = {}
self.on_frame = on_frame_default
self.on_call = on_call_default
self.on_notification = on_notification_default
def is_connected(self):
return self.client.connected
def notify(self, target, notification):
return self.client.send(target, notification)
def call0(self, target, request):
request.header = RPC_REQUEST_HEADER + b'\x00\x00\x00\x00' + \
request.method + b'\x00'
return self.client.send(target, request)
async def call(self, target, request):
async with self.call_lock:
call_id = self.call_id + 1
if call_id == 0xffff_ffff:
self.call_id = 0
else:
self.call_id = call_id
call_event = RpcCallEvent()
self.calls[call_id] = call_event
request.header = RPC_REQUEST_HEADER + call_id.to_bytes(
4, 'little') + request.method + b'\x00'
try:
try:
code = await (await self.client.send(
target,
request)).wait_completed(timeout=self.client.timeout)
if code != RESPONSE_OK:
try:
del self.calls[call_id]
except KeyError:
pass
err_code = -32000 - code
call_event.error = RpcException('RPC error', code=err_code)
call_event.completed.set()
except asyncio.TimeoutError:
try:
del self.calls[call_id]
except KeyError:
pass
err_code = -32000 - ERR_TIMEOUT
call_event.error = RpcException('RPC timeout', code=err_code)
call_event.completed.set()
except Exception as e:
try:
del self.calls[call_id]
except KeyError:
pass
call_event.error = RpcException(str(e), code=-32000 - ERR_IO)
call_event.completed.set()
return call_event
def __getattr__(self, method):
return functools.partial(self.params_call, method=method)
async def params_call(self, target, method: str, **kwargs):
params = kwargs
if '_raw' in kwargs:
params = kwargs.pop('_raw')
params = params or None
c0 = False
if method.endswith('0'):
c0 = True
method = method[:-1]
request = Request(method, serialize(params))
if c0:
await self.call0(target, request)
return None
result = await self.call(target, request)
return deserialize((await result.wait_completed()).get_payload())
async def _handle_frame(self, frame):
try:
if frame.type == OP_MESSAGE:
if frame.payload[0] == RPC_NOTIFICATION:
event = Event(RPC_NOTIFICATION, frame, 1)
await self.on_notification(event)
elif frame.payload[0] == RPC_REQUEST:
sender = frame.sender
call_id_b = frame.payload[1:5]
call_id = int.from_bytes(call_id_b, 'little')
method = frame.payload[5:5 +
frame.payload[5:].index(b'\x00')]
event = Event(RPC_REQUEST, frame, 6 + len(method))
event.call_id = call_id
event.method = method
if call_id == 0:
await self.on_call(event)
else:
reply = Reply()
try:
reply.payload = await self.on_call(event)
if reply.payload is None:
reply.payload = b''
reply.header = RPC_REPLY_HEADER + call_id_b
except Exception as e:
code = getattr(e, 'rpc_error_code', RPC_ERROR_CODE_INTERNAL)
reply.header = (
RPC_ERROR_REPLY_HEADER + call_id_b +
code.to_bytes(2, 'little', signed=True))
reply.payload = format_rpc_e_msg(e)
await self.client.send(sender, reply)
elif frame.payload[0] == RPC_REPLY or frame.payload[
0] == RPC_ERROR:
call_id = int.from_bytes(frame.payload[1:5], 'little')
try:
call_event = self.calls.pop(call_id)
call_event.frame = frame
if frame.payload[0] == RPC_ERROR:
err_code = int.from_bytes(frame.payload[5:7],
'little',
signed=True)
call_event.error = RpcException(frame.payload[7:],
code=err_code)
call_event.completed.set()
except KeyError:
logger.warning(f'orphaned RPC response: {call_id}')
else:
logger.error(f'Invalid RPC frame code: {frame.payload[0]}')
else:
await self.on_frame(frame)
except Exception:
import traceback
logger.error(traceback.format_exc())
class RpcCallEvent:
def __init__(self):
self.frame = None
self.error = None
self.completed = asyncio.Event()
def is_completed(self):
return self.completed.is_set()
async def wait_completed(self, timeout=None):
if timeout:
if not await asyncio.wait_for(self.completed.wait(),
timeout=timeout):
raise TimeoutError
else:
await self.completed.wait()
if self.error is None:
return self
else:
raise self.error
def get_payload(self):
return self.frame.payload[5:]
def is_empty(self):
return len(self.frame.payload) <= 5
class Event:
def __init__(self, tp, frame, payload_pos):
self.tp = tp
self.frame = frame
self._payload_pos = payload_pos
def get_payload(self):
return self.frame.payload[self._payload_pos:]
class Notification:
def __init__(self, payload=b''):
self.payload = payload
self.type = OP_MESSAGE
self.qos = 1
self.header = RPC_NOTIFICATION_HEADER
class Request:
def __init__(self, method, params=b''):
self.payload = b'' if params is None else params
self.type = OP_MESSAGE
self.qos = 1
self.method = method.encode() if isinstance(method, str) else method
self.header = None
class Reply:
def __init__(self, result=b''):
self.payload = result
self.type = OP_MESSAGE
self.qos = 1
self.header = None
__all__ = ["Rpc", "Request", "Reply", "RpcCallEvent", "Event", "RpcException"]

@ -0,0 +1,14 @@
import msgpack
import zstd
def deserialize(data):
if data is None:
return None
return msgpack.unpackb(zstd.decompress(data))
def serialize(data):
if data is None:
data={}
return zstd.compress(msgpack.dumps(data))

@ -0,0 +1,2 @@
from .container import *
from .inject import *

@ -0,0 +1 @@
__version__ = "0.6.6"

@ -0,0 +1,97 @@
from types import LambdaType
from typing import Any, Callable, Dict, List, Type, Union
from .errors.service_error import ServiceError
_MISSING_SERVICE = object()
class Container:
def __init__(self):
self._memoized_services: Dict[Union[str, Type], Any] = {}
self._services: Dict[Union[str, Type], Any] = {}
self._factories: Dict[Union[str, Type], Callable[[Container], Any]] = {}
self._aliases: Dict[Union[str, Type], List[Union[str, Type]]] = {}
def __setitem__(self, key: Union[str, Type], value: Any) -> None:
self._services[key] = value
if key in self._memoized_services:
del self._memoized_services[key]
def add_alias(self, name: Union[str, Type], target: Union[str, Type]):
if List[target] in self._memoized_services: # type: ignore
del self._memoized_services[List[target]] # type: ignore
if name not in self._aliases:
self._aliases[name] = []
self._aliases[name].append(target)
def __getitem__(self, key: Union[str, Type]) -> Any:
if key in self._factories:
return self._factories[key](self)
service = self._get(key)
if service is not _MISSING_SERVICE:
return service
if key in self._aliases:
unaliased_key = self._aliases[key][0] # By default return first aliased service
if unaliased_key in self._factories:
return self._factories[unaliased_key](self)
service = self._get(unaliased_key)
# service = self._get(self._aliases[key][0]) # By default return first aliased service
if service is not _MISSING_SERVICE:
return service
# Support aliasing
if self._has_alias_list_for(key):
result = [self._get(alias) for alias in self._aliases[key.__args__[0]]] # type: ignore
self._memoized_services[key] = result
return result
raise ServiceError(f"Service {key} is not registered.")
def _get(self, key: Union[str, Type]) -> Any:
if key in self._memoized_services:
return self._memoized_services[key]
if key not in self._services:
return _MISSING_SERVICE
value = self._services[key]
if isinstance(value, LambdaType) and value.__name__ == "<lambda>":
self._memoized_services[key] = value(self)
return self._memoized_services[key]
return value
def __contains__(self, key) -> bool:
contains = key in self._services or key in self._factories or key in self._aliases
if contains:
return contains
if self._has_alias_list_for(key):
return True
return False
def _has_alias_list_for(self, key: Union[str, Type]) -> bool:
return hasattr(key, "__origin__") and hasattr(key, "__args__") and key.__origin__ == list and key.__args__[0] in self._aliases # type: ignore
@property
def factories(self) -> Dict[Union[str, Type], Callable[["Container"], Any]]:
return self._factories
def clear_cache(self) -> None:
self._memoized_services = {}
di: Container = Container()
__all__ = ["Container", "di"]

@ -0,0 +1,4 @@
from .conainer_error import ContainerError
from .execution_error import ExecutionError
from .resolver_error import ResolverError
from .service_error import ServiceError

@ -0,0 +1,2 @@
class ContainerError(RuntimeError):
pass

@ -0,0 +1,5 @@
from .conainer_error import ContainerError
class ExecutionError(ContainerError):
pass

@ -0,0 +1,5 @@
from .conainer_error import ContainerError
class ResolverError(ContainerError):
pass

@ -0,0 +1,5 @@
from .conainer_error import ContainerError
class ServiceError(ContainerError, KeyError):
pass

@ -0,0 +1,245 @@
import asyncio
import sys
from abc import ABC
from functools import wraps
from inspect import Parameter as InspectParameter, isclass, signature
from typing import Any, Callable, Dict, ForwardRef, NewType, Tuple, Type, TypeVar, Union # type: ignore
from typing_extensions import Protocol
from .container import Container, di
from .errors import ExecutionError
T = TypeVar("T")
S = TypeVar("S")
ServiceDefinition = Union[Type[S], Callable]
ServiceResult = Union[S, Callable]
Undefined = NewType("Undefined", int)
class _ProtocolInit(Protocol):
pass
_no_init = _ProtocolInit.__init__
def _resolve_forward_reference(module: Any, ref: Union[str, ForwardRef]) -> Any:
if isinstance(ref, str):
name = ref
else:
name = ref.__forward_arg__
if name in sys.modules[module].__dict__:
return sys.modules[module].__dict__[name]
return None
class Parameter:
type: Any
name: str
default: Any
def __init__(self, name: str, type: Any = Any, default: Any = Undefined):
self.name = name
self.type = type
self.default = default
def _inspect_function_arguments(
function: Callable,
) -> Tuple[Tuple[str, ...], Dict[str, Parameter]]:
parameters_name: Tuple[str, ...] = tuple(signature(function).parameters.keys())
parameters = {}
for name, parameter in signature(function).parameters.items():
if isinstance(parameter.annotation, (str, ForwardRef)) and hasattr(function, "__module__"):
annotation = _resolve_forward_reference(function.__module__, parameter.annotation)
else:
annotation = parameter.annotation
parameters[name] = Parameter(
parameter.name,
annotation,
parameter.default if parameter.default is not InspectParameter.empty else Undefined,
)
return parameters_name, parameters
def _resolve_function_kwargs(
alias_map: Dict[str, str],
parameters_name: Tuple[str, ...],
parameters: Dict[str, Parameter],
container: Container,
) -> Dict[str, Any]:
resolved_kwargs = {}
for name in parameters_name:
if name in alias_map and alias_map[name] in container:
resolved_kwargs[name] = container[alias_map[name]]
continue
if name in container:
resolved_kwargs[name] = container[name]
continue
if parameters[name].type in container:
resolved_kwargs[name] = container[parameters[name].type]
continue
if parameters[name].default is not Undefined:
resolved_kwargs[name] = parameters[name].default
return resolved_kwargs
def _decorate(binding: Dict[str, Any], service: ServiceDefinition, container: Container) -> ServiceResult:
# ignore abstract class initialiser and protocol initialisers
if (
service in [ABC.__init__, _no_init] or service.__name__ == "_no_init"
): # FIXME: fix this when typing_extensions library gets fixed
return service
# Add class definition to dependency injection
parameters_name, parameters = _inspect_function_arguments(service)
def _resolve_kwargs(args, kwargs) -> dict:
# attach named arguments
passed_kwargs = {**kwargs}
# resolve positional arguments
if args:
for key, value in enumerate(args):
passed_kwargs[parameters_name[key]] = value
# prioritise passed kwargs and args resolving
if len(passed_kwargs) == len(parameters_name):
return passed_kwargs
resolved_kwargs = _resolve_function_kwargs(binding, parameters_name, parameters, container)
all_kwargs = {**resolved_kwargs, **passed_kwargs}
if len(all_kwargs) < len(parameters_name):
missing_parameters = [arg for arg in parameters_name if arg not in all_kwargs]
raise ExecutionError(
"Cannot execute function without required parameters. "
+ f"Did you forget to bind the following parameters: `{'`, `'.join(missing_parameters)}`?"
)
return all_kwargs
@wraps(service)
def _decorated(*args, **kwargs):
# all arguments were passed
if len(args) == len(parameters_name):
return service(*args, **kwargs)
if parameters_name == tuple(kwargs.keys()):
return service(**kwargs)
all_kwargs = _resolve_kwargs(args, kwargs)
return service(**all_kwargs)
@wraps(service)
async def _async_decorated(*args, **kwargs):
# all arguments were passed
if len(args) == len(parameters_name):
return await service(*args)
if parameters_name == tuple(kwargs.keys()):
return await service(**kwargs)
all_kwargs = _resolve_kwargs(args, kwargs)
return await service(**all_kwargs)
if asyncio.iscoroutinefunction(service):
return _async_decorated
return _decorated
def inject(
_service: ServiceDefinition = None,
alias: Any = None,
bind: Dict[str, Any] = None,
container: Container = di,
use_factory: bool = False,
) -> Union[ServiceResult, Callable[[ServiceDefinition], ServiceResult]]:
def _decorator(_service: ServiceDefinition) -> ServiceResult:
if isclass(_service):
setattr(
_service,
"__init__",
_decorate(bind or {}, getattr(_service, "__init__"), container),
)
if use_factory:
container.factories[_service] = lambda _di: _service()
if alias:
container.add_alias(alias, _service)
else:
container[_service] = lambda _di: _service()
if alias:
container.add_alias(alias, _service)
return _service
service_function = _decorate(bind or {}, _service, container)
container[service_function.__name__] = service_function
if alias:
container.add_alias(alias, service_function.__name__)
return service_function
if _service is None:
return _decorator
return _decorator(_service)
def provider(
_service: ServiceDefinition = None,
alias: Any = None,
bind: Dict[str, Any] = None,
container: Container = di,
process: bool = False,
) -> Union[ServiceResult, Callable[[ServiceDefinition], ServiceResult]]:
def _decorator(_service: ServiceDefinition) -> ServiceResult:
if isclass(_service):
setattr(
_service,
"__init__",
_decorate(bind or {}, getattr(_service, "__init__"), container),
)
if process:
container.factories[_service] = lambda _di: _service()
if alias:
container.add_alias(alias, _service)
else:
container[_service] = lambda _di: _service()
if alias:
container.add_alias(alias, _service)
return _service
service_function = _decorate(bind or {}, _service, container)
if process:
container.factories[service_function.__name__] = lambda x: service_function()
else:
container[service_function.__name__] = lambda x: service_function()
if alias:
container.add_alias(alias, service_function.__name__)
return service_function
if _service is None:
return _decorator
return _decorator(_service)
__all__ = ["inject", "provider"]

@ -0,0 +1,124 @@
import asyncio
import time
from collections.abc import AsyncIterator
from typing import Any, Awaitable, Callable, Coroutine, Optional, Set, Union
AsyncFuncType = Callable[[Any, Any], Awaitable[Any]]
class ClockTicker(AsyncIterator):
"""
T - A clock tick
F - Something that happens inside an iteration ("x" = running "-" = waiting)
I - A clock iteration
E.g:
async for tick in Clock(seconds=2):
await asyncio.sleep(3)
T: 15------17------19------21------23------25------27------29------
F: xxxxxxxxxxxxx---xxxxxxxxxxxxx---xxxxxxxxxxxxx---xxxxxxxxxxxxx---
I: x---------------x---------------x---------------x---------------
"""
def __init__(self, seconds: Union[float, int]) -> None:
"""
:param seconds: Tick interval in seconds
"""
self.seconds = seconds
self.current_iteration = 0
self._tick_event = asyncio.Event()
self._running: Optional[bool] = None
self._main_task: Optional[asyncio.Future] = None
def __aiter__(self) -> AsyncIterator:
if self._running is not None:
raise RuntimeError("Cannot reuse a clock instance.")
self._running = True
self._main_task = asyncio.ensure_future(self._run())
return self
async def __anext__(self) -> int:
if not self._running:
raise StopAsyncIteration
self._tick_event.clear()
await self._tick_event.wait()
i = self.current_iteration
self.current_iteration += 1
return i
async def _run(self) -> None:
while self._running:
self._tick_event.set()
await asyncio.sleep(self.seconds)
self._tick_event.clear()
async def stop(self) -> None:
self._running = False
if self._main_task:
await self._main_task
def perf_counter_ms() -> float:
"""
Return the value (in fractional milliseconds) of a performance counter,
i.e. a clock with the highest available resolution to measure a short
duration. It does include time elapsed during sleep and is system-wide.
The reference point of the returned value is undefined, so that only the
difference between the results of consecutive calls is valid.
"""
return time.perf_counter() * 1000
class ScheduledTaskRunner:
def __init__(
self,
seconds: int,
task: Callable[[], Coroutine],
max_concurrency: int,
) -> None:
self.seconds = seconds
self.max_concurrency = max_concurrency
self.task = task
self.running_tasks: Set[asyncio.Future] = set()
self.task_is_done_event = asyncio.Event()
self._started = False
self.clock = ClockTicker(seconds=self.seconds)
async def can_dispatch_task(self) -> bool:
if len(self.running_tasks) < self.max_concurrency:
return True
if await self.task_is_done_event.wait():
return True
return False
async def _wrapped_task(self) -> None:
"""
Wraps the future task on a coroutine that's responsible for unregistering
itself from the "running tasks" and emitting an "task is done" event
"""
try:
await self.task()
finally:
self.task_is_done_event.set()
self.running_tasks.remove(asyncio.current_task()) # type: ignore
async def start(self,*args,**kwargs) -> asyncio.Future:
self._started = True
return asyncio.ensure_future(self._run())
async def stop(self,*args,**kwargs) -> None:
await self.clock.stop()
await asyncio.gather(*self.running_tasks)
async def _run(self) -> None:
async for _ in self.clock:
if await self.can_dispatch_task():
task = asyncio.ensure_future(self._wrapped_task())
self.running_tasks.add(task)
self.task_is_done_event.clear()

@ -0,0 +1,166 @@
class RadixTreeNode(object):
def __init__(self, path=None, handler=None, methods=None):
self.path = path
self.methods = {}
self.children = []
self.indices = str()
self.size = 0
self.add_methods(methods, handler)
def __repr__(self):
return ('<RadixTreeNode path: {}, methods: {}, indices: {}, children: '
'{}>'.format(self.path, self.methods, self.indices,
self.children))
def add_methods(self, methods, handler):
if not methods:
return
if not isinstance(methods, (list, tuple, set)):
methods = [methods]
for method in methods:
if method in self.methods and self.methods[method] != handler:
raise KeyError(
'{} conflicts with existed handler '
'{}'.format(handler, self.methods[method]))
self.methods[method] = handler
def bisect(self, target):
low, high = 0, self.size
while low < high:
mid = low + high >> 1
if self.indices[mid] < target:
low = mid + 1
else:
high = mid
return low
def insert_child(self, index, child):
pos = self.bisect(index)
self.indices = self.indices[:pos] + index + self.indices[pos:]
self.children.insert(pos, child)
self.size += 1
return child
def get_child(self, index):
for i, char in enumerate(self.indices):
if char == index:
return self.children[i]
class RadixTree(object):
def __init__(self):
self.root = RadixTreeNode()
def __repr__(self):
return repr(self.root)
def insert(self, key, handler, methods):
i, n, root = 0, len(key), self.root
def get_position(i):
return n if i == -1 else i
while i < n:
conflict, num = [], (key[i] == ':') + (root.indices == ':')
if (root.indices == '*' or
key[i] == '*' and root.indices or
num == 1 or
num == 2 and key[i + 1:get_position(
key.find('/', i))] != root.get_child(':').path):
conflict = [key[:i] + p for p in self.traverse(root)]
if conflict:
raise Exception('"{}" conflicts with {}'.format(key, conflict))
child = root.get_child(key[i])
if child is None:
pos = get_position(key.find(':', i))
if pos == n:
pos = get_position(key.find('*', i))
if pos == n:
root.insert_child(
key[i], RadixTreeNode(key[i:], handler, methods))
return
root = root.insert_child(key[i], RadixTreeNode(key[i:pos]))
root.insert_child(
'*', RadixTreeNode(key[pos + 1:], handler, methods))
return
root = root.insert_child(key[i], RadixTreeNode(key[i:pos]))
i = get_position(key.find('/', pos))
root = root.insert_child(':', RadixTreeNode(key[pos + 1:i]))
if i == n:
root.add_methods(methods, handler)
else:
root = child
if key[i] == ':':
i += len(root.path) + 1
if i == n:
root.add_methods(methods, handler)
else:
j, m = 0, len(root.path)
while i < n and j < m and key[i] == root.path[j]:
i += 1
j += 1
if j < m:
child = RadixTreeNode(root.path[j:])
child.methods = root.methods
child.children = root.children
child.indices = root.indices
child.size = root.size
root.path = root.path[:j]
root.methods = {}
root.children = [child]
root.indices = child.path[0]
root.size = 1
if i == n:
root.add_methods(methods, handler)
def get(self, key, method):
i, n, root, params = 0, len(key), self.root, {}
while i < n:
if root.indices == ':':
root, pos = root.children[0], key.find('/', i)
if pos == -1:
pos = n
params[root.path], i = key[i:pos], pos
elif root.indices == '*':
root = root.children[0]
params[root.path] = key[i:]
break
else:
root = root.get_child(key[i])
if root is None:
return False, None, {}
pos = i + len(root.path)
if key[i:pos] != root.path:
return False, None, {}
i = pos
return True, root.methods.get(method, None), params
def traverse(self, root):
r = []
for i, char in enumerate(root.indices):
child = root.children[i]
path = '{}{}'.format(
char if char in [':', '*'] else '', child.path)
if child.methods and child.indices:
r.append([path])
r.append([path + p for p in self.traverse(child) or ['']])
return sum(r, [])

@ -0,0 +1,32 @@
import asyncio
from loguru import logger
from busrtworker import App, ConnectionInfo
app = App()
api_ci = ConnectionInfo('api', 'localhost:9800', 'busrt.worker.test', static=True, topic='test/#')
caller_ci = ConnectionInfo('caller', 'localhost:9800', 'busrt.worker.test', static=True)
api = app.registry(api_ci)
app.registry(caller_ci)
@api.on_call()
def add(a, b):
return a + b
@api.subscribe('test/:name')
def print_name(name: str):
logger.info(f'{name} pub message')
@app.run_on_startup
async def test(server):
async def call():
await asyncio.sleep(1)
logger.info(f'call remote add result {(await app.caller.add(api_ci.final_name,a=1, b=2))}')
await app.caller.send('test/i_am_caller')
asyncio.create_task(call())
app.run()

247
poetry.lock generated

@ -0,0 +1,247 @@
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[package.source]
type = "legacy"
url = "https://mirror.baidu.com/pypi/simple"
reference = "baidu"
[[package]]
name = "loguru"
version = "0.7.0"
description = "Python logging made (stupidly) simple"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
{file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"},
{file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"},
]
[package.dependencies]
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"]
[package.source]
type = "legacy"
url = "https://mirror.baidu.com/pypi/simple"
reference = "baidu"
[[package]]
name = "msgpack"
version = "1.0.5"
description = "MessagePack serializer"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"},
{file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"},
{file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"},
{file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"},
{file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"},
{file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"},
{file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"},
{file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"},
{file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"},
{file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"},
{file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"},
{file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"},
{file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"},
{file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"},
{file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"},
{file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"},
{file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"},
{file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"},
{file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"},
{file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"},
{file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"},
{file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"},
{file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"},
{file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"},
{file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"},
{file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"},
{file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"},
{file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"},
{file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"},
{file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"},
{file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"},
{file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"},
{file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"},
{file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"},
{file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"},
{file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"},
{file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"},
{file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"},
{file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"},
{file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"},
{file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"},
{file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"},
{file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"},
{file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"},
{file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"},
{file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"},
{file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"},
{file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"},
{file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"},
{file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"},
{file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"},
{file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"},
{file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"},
{file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"},
{file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"},
{file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"},
{file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"},
{file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"},
{file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"},
{file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"},
{file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"},
{file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"},
{file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"},
]
[package.source]
type = "legacy"
url = "https://mirror.baidu.com/pypi/simple"
reference = "baidu"
[[package]]
name = "win32-setctime"
version = "1.1.0"
description = "A small Python utility to set file creation time on Windows"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[package.source]
type = "legacy"
url = "https://mirror.baidu.com/pypi/simple"
reference = "baidu"
[[package]]
name = "zstd"
version = "1.5.5.1"
description = "ZSTD Bindings for Python"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "zstd-1.5.5.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:555779789bc75cd05089c3ba857f45a0a8c4b87d45e5ced02fec77fa8719237a"},
{file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:86496bd4830cdb7b4b05a9ce6ce2baee87d327ff90845da4ee308452bfbbed4e"},
{file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b487c2e67ed42a4e0d47997d209f4456b01b334023083ef61873f79577c84c62"},
{file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:45ccd45a5b681088fca1a863ca9236ded5112b8011f1d5bf69e908f5eb32023a"},
{file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8403fe84207d8b0c7b17bca6c4caad431ac765b1b9b626ad9fae4bb93a64a9d8"},
{file = "zstd-1.5.5.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:0ab979c6357b8927f0c025ea2f72f25e15d03ce17a8a6c1789e2d5b108bf39ae"},
{file = "zstd-1.5.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:98cbee6c1b2fe85f02fd475d885f98363c63bc64eebc249d7eb7469a0ff70283"},
{file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9962714b89641301029f3832bdf07c20f60b9e64e39e8d7b6253451a82b54f5c"},
{file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f59cc92d71537f8082306f75aa403ddb4a4a1069a39f104525673110e4d23f7"},
{file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:569f13d0c926ddafceebce8ac73baddfc2bd9cbbbbc922b6b3073338cc43dae6"},
{file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba530c44f252016acc6ef906d7d2070c1ad0cfe835c498fdcd37493e4772ac6e"},
{file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee3496ed8fff3add6c6e658b207f18d96474c3db0c28ab7a69623380b1a0a8c"},
{file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:530d69bea2791cde8afa7fe988f3a37c3ba37015f6a1d5593c0500f089f3090e"},
{file = "zstd-1.5.5.1-cp310-cp310-win32.whl", hash = "sha256:cf179e51f447b6a7ff47e449fcb98fb5fe15aedcc90401697cf7c93dd6e4434e"},
{file = "zstd-1.5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:5f5e6e0805d710d7509c8d175a467eb89c631a4142b1a630ceeb8e3e3138d152"},
{file = "zstd-1.5.5.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:022f935a8666e08f0fff6204938a84d9fe4fcd8235a205787275933a07a164fb"},
{file = "zstd-1.5.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3d15a2d18dac8bcafdde52fdf5d40ecae1f73b7de19b171f42339d2e51346d0"},
{file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b9c67989f50ba63ffa0c50c9eaa037c2d14abacb0813e838ad705135245b4b"},
{file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97da6a842ba7e4acf8bba7c596057143ee39b3c4a467196c2096d460e44accd6"},
{file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dafd492fb8ee4ae04c81ab00f5f137860e7071f611335dd4cdb1c38bd8f11bc"},
{file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9ee83e0bcbfd776200b026b3b9e86c6c86b8f414749f58d87c85dcf456b27066"},
{file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ae2fd4bc8ea772a7b5f1acd1cac9e34bb9cd8fcde191f170092fdeea779a3a12"},
{file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:edea52a0109f48fd46f4763689d3d356dcafd20ddf6789c559a1bd2e62b40a32"},
{file = "zstd-1.5.5.1-cp311-cp311-win32.whl", hash = "sha256:88410481209520298ec4430e0d1d57e004c45e0b27c3035674fb182ccd2d8b7b"},
{file = "zstd-1.5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:dce18aaefbacf8b133367be86beec670baf68c0420bfcca49be08dbdbf933db6"},
{file = "zstd-1.5.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:634dc632f7cf87e95dabf74dcf682e3507bd5cb9dd1bcdb81f92a6521aab0bd2"},
{file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:608414eb75ead573891d97a1e529848b8f31749d21a440e80838548a19d8c0e6"},
{file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384128f7a731e3f45da49976591cec03fc4079e70653df10d9ea43a1d3b49d50"},
{file = "zstd-1.5.5.1-cp35-cp35m-win32.whl", hash = "sha256:4bce254174ef05cea01021d67e18489d5d08db1168e758b62ecee121572a52a9"},
{file = "zstd-1.5.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3f0ff81232b49d7eb4f4d9e6f92443c9d242c139ad98ffedac0e889568f900ce"},
{file = "zstd-1.5.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a871df41b801a260cc849c2c76f300ebb9d286c4b7a1fd6ce45fe0c91340b767"},
{file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a53860dbfbea281eb690ce09cae28967cf1df8e6d7560e4a8bf5b9fcb258147"},
{file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a37cbc0580fdfd66c8b3ec65f9af00a4a34e9781b54dfb89f04d301dc375c90a"},
{file = "zstd-1.5.5.1-cp36-cp36m-win32.whl", hash = "sha256:5531b683539ae1f7b2ad23dacee8a73e5d7eaa6702ea8df5a24bd3318647dee1"},
{file = "zstd-1.5.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eeaff418269b41eee8c7971fbba9d32d07d3f6aa26f962a72aff725071096a1b"},
{file = "zstd-1.5.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8bd6a9050de8bbe844447348372ca17d01bc05207619f6a5d448567d111b5cd9"},
{file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ece3d20ef357370584f304407fbd1e4ff9c231209320e08a889b8e3725d56e"},
{file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687f9e03dc9f9b8803840425bb23bf6bc700888b4860afcf43c4f238102752d2"},
{file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a649daac9c8f1b37d29f2b3d0a43f134061659b54877fe4b0da6df2965dc91f"},
{file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bddc7e3c3ce31c01fe1edaa7c03c0b9e71eadf4ce1609746d32f86d95a0449e6"},
{file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:12bf8e04add8bb84f9fe9117f3de6d9394eade6a5a82fe4d6bd95914fc6ef423"},
{file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e6a15fa4d2e65c5902ab2a4e41279ac126cb371ce6c3c75ad5789bb20dd1f54"},
{file = "zstd-1.5.5.1-cp37-cp37m-win32.whl", hash = "sha256:a1c269243a4321beb948635b544ccbe6390846358ace620fd000ab7099011d9c"},
{file = "zstd-1.5.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:91366e36773241cb4b049a32f4495d33dd274df1eea5b55396f5f3984a3de22e"},
{file = "zstd-1.5.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:d3ce2cb310690994274d133ea7f269dd4b81799fdbce158690556209723d7d4e"},
{file = "zstd-1.5.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e0c87bfbfa9d852f79c90bcd7426c3ba46cf3285e6984013636d4fc854ba9230"},
{file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce6d829d515f272fddb3a87e1a5f32cc0f1a7b0cba24d360c89f4a165b74b"},
{file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e05f81f346213b23ed1b12d84fc1f72e65eacd8978e1e88facf185c82bd3d053"},
{file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ec66c4c3a76351c672c6ef9f0ff3412fca9ede0a56d18dddaf6418a93faef8"},
{file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:58e554e91e0d49f4f2b2df390cdd0f64aa9b6fd5f4dcb208c094bfd079b30f3a"},
{file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:883c6d3b6f5574e1765ca97f4b6a41b69094a41be56175552faebc0e0e43b65e"},
{file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d52b6932cab5419c434bccfea3e5640e755369fc9eeb51e3d17e15bf8e8cb103"},
{file = "zstd-1.5.5.1-cp38-cp38-win32.whl", hash = "sha256:dcaf44270ec88552e969be4dd3359b34aa3065663ccd8168a257c78f150a356c"},
{file = "zstd-1.5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:627f12cb7035723c8f3d8d4cefcad6d950ed9cba33fd3eb46bae04ccab479234"},
{file = "zstd-1.5.5.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:c0dab132c1a5a7cc838a7c3e4e380ad153b9d7bd1fadafabf6cfeb780b916201"},
{file = "zstd-1.5.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4ab0a5dd9a41d3b083304beee7ada40ee36431acbeb75132032f4fe5cf0490a"},
{file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6e38f496d287020658c6b4cdb5e815ecc6998889bd0f1f9ab0825f2e3d74ef"},
{file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0096c8ee0ed4bfe406bc961019f55552109e19771bfd3eb32d2af56ea27085c"},
{file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a0f1527728c50b6aa8f04b47a07580f0ae13cfc6c6d9c96bb0bdf5259487559"},
{file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6a64e420c904063c5c3de53c00ec0993ebc0a48cebbef97dc6c768562c5abab5"},
{file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03444e357b7632c64480a81ce7095242dab9d7f8aed317326563ef6c663263eb"},
{file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88b9a10f80d2b87bf8cc1a1fc20a815ed92b5eefdc15cbe8062021f0b5a26a10"},
{file = "zstd-1.5.5.1-cp39-cp39-win32.whl", hash = "sha256:c91cc1606eb8b3a6fed11faaef4c6e55f1133d70cf0db0c829a2cf9c2ac1dfd9"},
{file = "zstd-1.5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:f462e2ebf26dcbfc2c8dddd6b5c56859683f0b77edb8f268e637f7d390a58f74"},
{file = "zstd-1.5.5.1-pp27-pypy_73-macosx_10_14_x86_64.whl", hash = "sha256:c63f916732e3e309e49ec95e7a0af5d37ff1321f3df2aac10e507bd2b56fceda"},
{file = "zstd-1.5.5.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:50d4850d758bb033df50722cc13ed913b2afcd5385250be4f3ffb79a26b319c3"},
{file = "zstd-1.5.5.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:0412d666515e78a91ada7e2d78e9dd6b25ddda1b41623b145b99653275c7f3ce"},
{file = "zstd-1.5.5.1-pp36-pypy36_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0ea91f74869a3cdcb2dde08f8f30ee3da72782c5d1737afed9c703232815864e"},
{file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:477548897dc2b8b595af7bec5f0f55dcba8e9a282335f687cc663b52b171357b"},
{file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c518938b57a56001ee04dcf79a432152f5bd431416f3b22819ba959bc6054d89"},
{file = "zstd-1.5.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:894a8fe0228d5e24dc286a8d98eb0ce2883f8e2e57f3b7e7619ebdb67967120a"},
{file = "zstd-1.5.5.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:42ec0a4ae9bedd9909fa4f580f3c800469da1b631faeaa94f204e1b66c767fa2"},
{file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56dedaa04ab8ecc23492972b12e0bf8529f64c9bceb28c11f43c2369c9768b3"},
{file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5b060770d796e4c01f5848b345c3cea8a177ab4e7cd95a1963a355042d429e1"},
{file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fea04805ef6e1cb93d6e5d6bbc7a03bc75a5c733fd352d5aaa81109986fdf1ef"},
{file = "zstd-1.5.5.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:405c28a35756e57a434bbd7ed29dc5e6490cd2fc2118cbf78b60eaebd134f5e9"},
{file = "zstd-1.5.5.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:c42e630443b01a891277426365a51a2aa630b059ce675992c70c1928d30eccb4"},
{file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1520d23f24f26cdfbcdb4dc86947446b8f694838bfce728d7fc4b3492397357c"},
{file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4730737f63cf802321743ded6acc85e747e7f5587c5ba2e51a760bf009f7de"},
{file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f8c014395e89ad7f67ffe873c0fa1d8e9b4dea8b1801d24e8d9ccd8259858d"},
{file = "zstd-1.5.5.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5d9ba4f6af0945809bfa3387c6a1208a22937a876521b9ec347e7183d623311b"},
{file = "zstd-1.5.5.1-pp39-pypy39_pp73-macosx_10_14_x86_64.whl", hash = "sha256:04dfd9f46b0b0b1bc413884fe028b726febcb726d4f66e3cf8afc00c2d9026bf"},
{file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af52436a2eb5caa925d95461973984cb34d472a963b6be1c0a9f2dfbafad096f"},
{file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610928b888a2e7ae9d2018ffa814859d47ec4ba75f89a1188ab4eb9232636ee5"},
{file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee3c9feea99c7f4ff43129a885da056b5aa0cde3f7876bf6397bfb9433f44352"},
{file = "zstd-1.5.5.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ac9768eeb3c6b530db93de2fec9b363776075dc8a00ee4049612ba5397ca8e"},
{file = "zstd-1.5.5.1.tar.gz", hash = "sha256:1ef980abf0e1e072b028d2d76ef95b476632651c96225cf30b619c6eef625672"},
]
[package.source]
type = "legacy"
url = "https://mirror.baidu.com/pypi/simple"
reference = "baidu"
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "04b38a8ef5153eb24a8822947e6897b5f9c2459eeda498ca54f243cfedb196f5"

@ -0,0 +1,43 @@
[tool.poetry]
name = "busrt-worker"
version = "0.1.0"
description = "busrt-worker is a Python-based async busrt message handle framework"
authors = ["JimZhang <zzl22100048@gmail.com>"]
readme = "README.md"
packages = [{include = "busrt_worker"}]
[tool.poetry.dependencies]
python = "^3.10"
msgpack = "^1.0.5"
zstd = "^1.5.5.1"
loguru = "^0.7.0"
[tool.poetry.group.dev.dependencies]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[[tool.poetry.source]]
name = "baidu"
url = "https://mirror.baidu.com/pypi/simple"
default = true
[tool.ruff]
exclude = [
"tests",
]
line-length = 120
select = [
"E",
"F",
"W",
"I"
]
target-version = "py310"
[tool.ruff.isort]
combine-as-imports = true
Loading…
Cancel
Save