diff --git a/.env b/.env index cd64bc6..c2c33a6 100644 --- a/.env +++ b/.env @@ -13,3 +13,7 @@ VERIFICATION_TOKEN=35LaoAPYTXRyBmjeaBvzRbRUsJSL1KBx # BITABLE BITABLE_TOKEN=XDmHbhbZcaY4gZsOvBJcLcHonKh TABLE_ID=tblwgGCDCE74Ugv6 + +# REDIS +BROKER_URL=redis://office-xbot-redis +RESULT_BACKEND=redis://office-xbot-redis diff --git a/Dockerfile b/Dockerfile index 948906d..0a1d991 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,4 +30,4 @@ COPY ./app /code/app # -CMD ["python", "/code/app/demo.py"] +# CMD ["python", "/code/app/demo.py"] diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..8ddb8ad --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,107 @@ +import os +import json +import time + +import lark_oapi as lark +from celery import Celery +from celery import Task + + +from flask import Flask +from dotenv import load_dotenv +from itertools import chain +from lark_oapi.adapter.flask import * +from lark_oapi.api.im.v1 import * +from lark_oapi.api.bitable.v1 import * + +from app.task import * +from app.utils import * +from app.task import * + +load_dotenv() + + +app_id = os.environ["APP_ID"] +app_secret = os.environ["APP_SECRET"] + +encrypt_key = os.environ["ENCRYPT_KEY"] +verification_token = os.environ["VERIFICATION_TOKEN"] + +bitable_token = os.environ["BITABLE_TOKEN"] +table_id = os.environ["TABLE_ID"] + +bitable_token = "IRggb4I0MaRffTsJ7qkcw1wxnBg" +table_id = "tbl5J3sXBNIyNvJ8" + +broker_url = os.environ["BROKER_URL"] +result_backend = os.environ["RESULT_BACKEND"] + + +# 创建client +client = lark.Client.builder().app_id(app_id).app_secret(app_secret).build() + +current_dir = os.path.dirname(os.path.abspath(__file__)) +cache_dir = os.path.join(current_dir, "cache") + +if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + +def do_p2_im_message_receive_v1(data: P2ImMessageReceiveV1) -> None: + process_message_callback.delay(client, bitable_token, table_id, cache_dir, data) + print(lark.JSON.marshal(data)) + + +def do_customized_event(data: lark.CustomizedEvent) -> None: + print(lark.JSON.marshal(data)) + + +handler = ( + lark.EventDispatcherHandler.builder( + encrypt_key, verification_token, lark.LogLevel.DEBUG + ) + .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) + .register_p1_customized_event("message", do_customized_event) + .build() +) + + +def create_app() -> Flask: + app = Flask(__name__) + + app.config.from_mapping( + CELERY=dict( + broker_url=broker_url, + result_backend=result_backend, + task_ignore_result=True, + broker_connection_retry_on_startup=True, + ), + ) + app.config.from_prefixed_env() + celery_init_app(app) + + @app.route("/event", methods=["POST"]) + def event(): + print("receive event: ", time.time()) + resp = handler.do(parse_req()) + return parse_resp(resp) + + @app.route("/test", methods=["GET"]) + def test(): + add.delay(1, 2) + return {"test": "ok"} + + return app + + +def celery_init_app(app: Flask) -> Celery: + class FlaskTask(Task): + def __call__(self, *args: object, **kwargs: object) -> object: + with app.app_context(): + return self.run(*args, **kwargs) + + celery_app = Celery(app.name, task_cls=FlaskTask) + celery_app.config_from_object(app.config["CELERY"]) + celery_app.set_default() + app.extensions["celery"] = celery_app + return celery_app diff --git a/app/__pycache__/__init__.cpython-310.pyc b/app/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0d08cec Binary files /dev/null and b/app/__pycache__/__init__.cpython-310.pyc differ diff --git a/app/__pycache__/make_celery.cpython-310.pyc b/app/__pycache__/make_celery.cpython-310.pyc new file mode 100644 index 0000000..5bcde86 Binary files /dev/null and b/app/__pycache__/make_celery.cpython-310.pyc differ diff --git a/app/__pycache__/task.cpython-310.pyc b/app/__pycache__/task.cpython-310.pyc new file mode 100644 index 0000000..d779ea8 Binary files /dev/null and b/app/__pycache__/task.cpython-310.pyc differ diff --git a/app/__pycache__/utils.cpython-310.pyc b/app/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..4005dcd Binary files /dev/null and b/app/__pycache__/utils.cpython-310.pyc differ diff --git a/app/demo.py b/app/demo.py deleted file mode 100644 index ddc8d11..0000000 --- a/app/demo.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import json -import time - -import lark_oapi as lark - -from flask import Flask -from dotenv import load_dotenv -from itertools import chain -from lark_oapi.adapter.flask import * -from lark_oapi.api.im.v1 import * -from lark_oapi.api.bitable.v1 import * - -from utils import * - - -load_dotenv() - - -app_id = os.environ["APP_ID"] -app_secret = os.environ["APP_SECRET"] - -encrypt_key = os.environ["ENCRYPT_KEY"] -verification_token = os.environ["VERIFICATION_TOKEN"] - -bitable_token = os.environ["BITABLE_TOKEN"] -table_id = os.environ["TABLE_ID"] - -app = Flask(__name__) - -# 创建client -client = lark.Client.builder().app_id(app_id).app_secret(app_secret).build() - -current_dir = os.path.dirname(os.path.abspath(__file__)) -cache_dir = os.path.join(current_dir, "cache") - -if not os.path.exists(cache_dir): - os.makedirs(cache_dir) - - -def do_p2_im_message_receive_v1(data: P2ImMessageReceiveV1) -> None: - print(lark.JSON.marshal(data)) - - # record = {} - # images = [] - # images_token = [] - # message_content = "" - # message_type = data.event.message.message_type - # content = json.loads(data.event.message.content) - - # if message_type == "post": - # content = content["content"] - # for item in chain.from_iterable(content): - # if item.get("tag") == "text": - # message_content += item["text"] - - # if item.get("tag") == "img": - # images.append(item["image_key"]) - - # if message_type == "text": - # message_content = content["text"].replace("@_user_1", "").strip() - - # sender_id = data.event.sender.sender_id.open_id - # record["问题概述"] = message_content - # record["创建人"] = sender_id - # message_id = data.event.message.message_id - - # # 对 image_key 处理并并上传至多维表格 - - # for image in images: - # image_path = get_message_resource(client, message_id, image, "image", cache_dir) - # print("下载文件成功:", image, image_path) - # file_token = upload_media(client, image_path, "bitable_image", bitable_token) - # print("上传文件成功:", file_token) - # images_token.append(file_token) - - # if images: - # record["附件"] = [{"file_token": image} for image in images_token] - - # record_id = add_record(client, bitable_token, table_id, record) - - # time.sleep(1) - - # update_data = {"创建人": {"id": sender_id}} - # update_record(client, bitable_token, table_id, record_id, update_data) - - # # content = {"text": "反馈的问题已经记录到多维表格, 稍后有小伙伴跟进"} - # # send_message(receive_id, content) - - # # 回复群组的消息 - # content = f'{{"text": "反馈的问题已经记录到多维表格, 稍后有小伙伴跟进"}}' - # send_group_message(client, message_id, content) - - -def do_customized_event(data: lark.CustomizedEvent) -> None: - print(lark.JSON.marshal(data)) - - -handler = ( - lark.EventDispatcherHandler.builder( - encrypt_key, verification_token, lark.LogLevel.DEBUG - ) - .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) - .register_p1_customized_event("message", do_customized_event) - .build() -) - - -@app.route("/event", methods=["POST"]) -def event(): - print("receive event: ", time.time()) - resp = handler.do(parse_req()) - return parse_resp(resp) - - -if __name__ == "__main__": - app.run(port=8080, debug=True, host="0.0.0.0") diff --git a/app/make_celery.py b/app/make_celery.py new file mode 100644 index 0000000..1878ad2 --- /dev/null +++ b/app/make_celery.py @@ -0,0 +1,4 @@ +from app import create_app + +flask_app = create_app() +celery_app = flask_app.extensions["celery"] diff --git a/app/task.py b/app/task.py new file mode 100644 index 0000000..1b6249c --- /dev/null +++ b/app/task.py @@ -0,0 +1,81 @@ +import time +import json + +from itertools import chain + +from celery import shared_task +from celery import Task + +from app.utils import * + + +@shared_task(ignore_result=False) +def add(a: int, b: int) -> int: + return a + b + + +@shared_task() +def block() -> None: + time.sleep(5) + + +@shared_task(bind=True, ignore_result=False) +def process(self: Task, total: int) -> object: + for i in range(total): + self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) + time.sleep(1) + + return {"current": total, "total": total} + + +@shared_task(include_result=False) +def process_message_callback(client, bitable_token, table_id, cache_dir, data) -> None: + record = {} + images = [] + images_token = [] + message_content = "" + message_type = data.event.message.message_type + content = json.loads(data.event.message.content) + + if message_type == "post": + content = content["content"] + for item in chain.from_iterable(content): + if item.get("tag") == "text": + message_content += item["text"] + + if item.get("tag") == "img": + images.append(item["image_key"]) + + if message_type == "text": + message_content = content["text"].replace("@_user_1", "").strip() + + sender_id = data.event.sender.sender_id.open_id + record["问题概述"] = message_content + record["创建人"] = sender_id + message_id = data.event.message.message_id + + # 对 image_key 处理并并上传至多维表格 + + for image in images: + image_path = get_message_resource(client, message_id, image, "image", cache_dir) + print("下载文件成功:", image, image_path) + file_token = upload_media(client, image_path, "bitable_image", bitable_token) + print("上传文件成功:", file_token) + images_token.append(file_token) + + if images: + record["附件"] = [{"file_token": image} for image in images_token] + + record_id = add_record(client, bitable_token, table_id, record) + + time.sleep(1) + + update_data = {"创建人": {"id": sender_id}} + update_record(client, bitable_token, table_id, record_id, update_data) + + # content = {"text": "反馈的问题已经记录到多维表格, 稍后有小伙伴跟进"} + # send_message(receive_id, content) + + # 回复群组的消息 + content = f'{{"text": "反馈的问题已经记录到多维表格, 稍后有小伙伴跟进"}}' + send_group_message(client, message_id, content) diff --git a/app/tmp.py b/app/tmp.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/tmp.py @@ -0,0 +1 @@ + diff --git a/docker-compose.yaml b/docker-compose.yaml index a5f1c2e..63d5fc9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,8 +1,9 @@ version: '3' services: - office-xbot-feishu: - container_name: office-xbot-feishu-0 + office-xbot-app: + container_name: office-xbot-app image: "${APP_IMAGE}" + command: "flask --app app:create_app run --debug --port 8080 --host 0.0.0.0" ports: - "0.0.0.0:8080:8080" environment: @@ -12,3 +13,29 @@ services: - VERIFICATION_TOKEN=${VERIFICATION_TOKEN} - BITABLE_TOKEN=${BITABLE_TOKEN} - TABLE_ID=${TABLE_ID} + - BROKER_URL=${BROKER_URL} + - RESULT_BACKEND=${RESULT_BACKEND} + + office-xbot-celery: + container_name: office-xbot-celery + image: "${APP_IMAGE}" + command: "celery -A app.make_celery worker --loglevel INFO" + environment: + - APP_ID=${APP_ID} + - APP_SECRET=${APP_SECRET} + - ENCRYPT_KEY=${ENCRYPT_KEY} + - VERIFICATION_TOKEN=${VERIFICATION_TOKEN} + - BITABLE_TOKEN=${BITABLE_TOKEN} + - TABLE_ID=${TABLE_ID} + - BROKER_URL=${BROKER_URL} + - RESULT_BACKEND=${RESULT_BACKEND} + + office-xbot-redis: + container_name: office-xbot-redis + image: redis:latest + ports: + - "6379:6379" + +networks: + office-xbot-network: + external: true diff --git a/poetry.lock b/poetry.lock index e162c8b..26d174e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,40 @@ -# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "amqp" +version = "5.2.0" +description = "Low-level AMQP client for Python (fork of amqplib)." +optional = false +python-versions = ">=3.6" +files = [ + {file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"}, + {file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"}, +] + +[package.dependencies] +vine = ">=5.0.0,<6.0.0" + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "billiard" +version = "4.2.0" +description = "Python multiprocessing fork with improvements and bugfixes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d"}, + {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, +] [[package]] name = "blinker" @@ -11,6 +47,61 @@ files = [ {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, ] +[[package]] +name = "celery" +version = "5.3.6" +description = "Distributed Task Queue." +optional = false +python-versions = ">=3.8" +files = [ + {file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"}, + {file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"}, +] + +[package.dependencies] +billiard = ">=4.2.0,<5.0" +click = ">=8.1.2,<9.0" +click-didyoumean = ">=0.3.0" +click-plugins = ">=1.1.1" +click-repl = ">=0.2.0" +kombu = ">=5.3.4,<6.0" +python-dateutil = ">=2.8.2" +tzdata = ">=2022.7" +vine = ">=5.1.0,<6.0" + +[package.extras] +arangodb = ["pyArango (>=2.0.2)"] +auth = ["cryptography (==41.0.5)"] +azureblockblob = ["azure-storage-blob (>=12.15.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (>=3.25.0,<4)"] +consul = ["python-consul2 (==0.1.5)"] +cosmosdbsql = ["pydocumentdb (==2.3.5)"] +couchbase = ["couchbase (>=3.0.0)"] +couchdb = ["pycouchdb (==1.14.2)"] +django = ["Django (>=2.2.28)"] +dynamodb = ["boto3 (>=1.26.143)"] +elasticsearch = ["elastic-transport (<=8.10.0)", "elasticsearch (<=8.11.0)"] +eventlet = ["eventlet (>=0.32.0)"] +gevent = ["gevent (>=1.5.0)"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +memcache = ["pylibmc (==1.6.3)"] +mongodb = ["pymongo[srv] (>=4.0.2)"] +msgpack = ["msgpack (==1.0.7)"] +pymemcache = ["python-memcached (==1.59)"] +pyro = ["pyro4 (==4.82)"] +pytest = ["pytest-celery (==0.0.0)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] +s3 = ["boto3 (>=1.26.143)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem (==4.1.5)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard (==0.22.0)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -135,6 +226,55 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-didyoumean" +version = "0.3.0" +description = "Enables git-like *did-you-mean* feature in click" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[package.dependencies] +click = ">=7" + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "click-repl" +version = "0.3.0" +description = "REPL plugin for Click" +optional = false +python-versions = ">=3.6" +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + +[package.dependencies] +click = ">=7.0" +prompt-toolkit = ">=3.0.36" + +[package.extras] +testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] + [[package]] name = "colorama" version = "0.4.6" @@ -207,6 +347,38 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "kombu" +version = "5.3.5" +description = "Messaging library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "kombu-5.3.5-py3-none-any.whl", hash = "sha256:0eac1bbb464afe6fb0924b21bf79460416d25d8abc52546d4f16cad94f789488"}, + {file = "kombu-5.3.5.tar.gz", hash = "sha256:30e470f1a6b49c70dc6f6d13c3e4cc4e178aa6c469ceb6bcd55645385fc84b93"}, +] + +[package.dependencies] +amqp = ">=5.1.1,<6.0.0" +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.10.0)"] +azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] +confluentkafka = ["confluent-kafka (>=2.2.0)"] +consul = ["python-consul2"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +mongodb = ["pymongo (>=4.1.1)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=2.8.0)"] + [[package]] name = "lark-oapi" version = "1.0.28" @@ -253,16 +425,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -295,6 +457,20 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "pycryptodome" version = "3.20.0" @@ -336,6 +512,20 @@ files = [ {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.0" @@ -350,6 +540,24 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "redis" +version = "5.0.1" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, + {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "requests" version = "2.31.0" @@ -385,6 +593,28 @@ files = [ [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tzdata" +version = "2023.4" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, +] + [[package]] name = "urllib3" version = "2.1.0" @@ -401,6 +631,28 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "vine" +version = "5.1.0" +description = "Python promises." +optional = false +python-versions = ">=3.6" +files = [ + {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, + {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [[package]] name = "werkzeug" version = "3.0.1" @@ -421,4 +673,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "95e9728a1492258abd1459823347f2a4775e15deea59c39ac1cc06052fe21c61" +content-hash = "52a8819fa1ada0e76b575b691fdc306fdca4671d11b3aa118a6f227a1535cf64" diff --git a/pyproject.toml b/pyproject.toml index 496cb50..5e15bfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,8 @@ python = "^3.10" flask = "^3.0.0" python-dotenv = "^1.0.0" lark-oapi = "^1.0.28" +celery = "^5.3.6" +redis = "^5.0.1" [build-system]