From 0fc2c291f0e17d9dabbdf70710b8448a75cf037b Mon Sep 17 00:00:00 2001 From: houlinfeng Date: Thu, 5 Sep 2024 15:02:39 +0800 Subject: [PATCH] feat: mineru_web --- projects/README.md | 2 + projects/web_api/README.md | 27 + projects/web_api/mineru-web接口文档.html | 65 ++ projects/web_api/poetry.lock | 687 ++++++++++++++++++ projects/web_api/pyproject.toml | 24 + projects/web_api/tests/__init__.py | 0 projects/web_api/web_api/__init__.py | 1 + projects/web_api/web_api/api/__init__.py | 36 + .../web_api/web_api/api/analysis/__init__.py | 18 + .../web_api/api/analysis/analysis_view.py | 231 ++++++ projects/web_api/web_api/api/analysis/ext.py | 25 + .../web_api/api/analysis/formula_ext.py | 280 +++++++ .../web_api/api/analysis/img_md_view.py | 46 ++ .../web_api/web_api/api/analysis/models.py | 29 + .../web_api/web_api/api/analysis/pdf_ext.py | 162 +++++ .../web_api/api/analysis/serialization.py | 28 + .../web_api/web_api/api/analysis/task_view.py | 95 +++ .../web_api/api/analysis/upload_view.py | 89 +++ projects/web_api/web_api/api/extentions.py | 61 ++ projects/web_api/web_api/app.py | 54 ++ projects/web_api/web_api/common/__init__.py | 0 .../web_api/web_api/common/custom_response.py | 23 + .../web_api/web_api/common/error_types.py | 45 ++ projects/web_api/web_api/common/ext.py | 80 ++ .../web_api/web_api/common/import_models.py | 1 + projects/web_api/web_api/common/logger.py | 19 + projects/web_api/web_api/common/web_hook.py | 9 + projects/web_api/web_api/config/__init__.py | 0 projects/web_api/web_api/config/config.yaml | 31 + projects/web_api/web_api/config/mineru_web.db | Bin 0 -> 557056 bytes projects/web_api/web_api/static/__init__.py | 0 31 files changed, 2168 insertions(+) create mode 100644 projects/web_api/README.md create mode 100644 projects/web_api/mineru-web接口文档.html create mode 100644 projects/web_api/poetry.lock create mode 100644 projects/web_api/pyproject.toml create mode 100644 projects/web_api/tests/__init__.py create mode 100644 projects/web_api/web_api/__init__.py create mode 100644 projects/web_api/web_api/api/__init__.py create mode 100644 projects/web_api/web_api/api/analysis/__init__.py create mode 100644 projects/web_api/web_api/api/analysis/analysis_view.py create mode 100644 projects/web_api/web_api/api/analysis/ext.py create mode 100644 projects/web_api/web_api/api/analysis/formula_ext.py create mode 100644 projects/web_api/web_api/api/analysis/img_md_view.py create mode 100644 projects/web_api/web_api/api/analysis/models.py create mode 100644 projects/web_api/web_api/api/analysis/pdf_ext.py create mode 100644 projects/web_api/web_api/api/analysis/serialization.py create mode 100644 projects/web_api/web_api/api/analysis/task_view.py create mode 100644 projects/web_api/web_api/api/analysis/upload_view.py create mode 100644 projects/web_api/web_api/api/extentions.py create mode 100644 projects/web_api/web_api/app.py create mode 100644 projects/web_api/web_api/common/__init__.py create mode 100644 projects/web_api/web_api/common/custom_response.py create mode 100644 projects/web_api/web_api/common/error_types.py create mode 100644 projects/web_api/web_api/common/ext.py create mode 100644 projects/web_api/web_api/common/import_models.py create mode 100644 projects/web_api/web_api/common/logger.py create mode 100644 projects/web_api/web_api/common/web_hook.py create mode 100644 projects/web_api/web_api/config/__init__.py create mode 100644 projects/web_api/web_api/config/config.yaml create mode 100644 projects/web_api/web_api/config/mineru_web.db create mode 100644 projects/web_api/web_api/static/__init__.py diff --git a/projects/README.md b/projects/README.md index dc5b5339..39964de6 100644 --- a/projects/README.md +++ b/projects/README.md @@ -3,3 +3,5 @@ ## 项目列表 - [llama_index_rag](./llama_index_rag/README.md): 基于 llama_index 构建轻量级 RAG 系统 + +- [web_api](./web_api/README.md): PDF解析的restful api服务 diff --git a/projects/web_api/README.md b/projects/web_api/README.md new file mode 100644 index 00000000..990fb767 --- /dev/null +++ b/projects/web_api/README.md @@ -0,0 +1,27 @@ +## 安装 + +MinerU + +```bash +# mineru已安装则跳过此步骤 + +git clone https://github.com/opendatalab/MinerU.git +cd MinerU + +conda create -n MinerU python=3.10 +conda activate MinerU +pip install .[full] --extra-index-url https://wheels.myhloli.com +``` + +第三方软件 + +```bash +cd projects/web_api +pip install poetry +portey install +``` + +接口文档 +``` +在浏览器打开 mineru-web接口文档.html +``` diff --git a/projects/web_api/mineru-web接口文档.html b/projects/web_api/mineru-web接口文档.html new file mode 100644 index 00000000..58466855 --- /dev/null +++ b/projects/web_api/mineru-web接口文档.html @@ -0,0 +1,65 @@ + + + + + + Apifox 接口文档 + + + + + + +
+ + + + + diff --git a/projects/web_api/poetry.lock b/projects/web_api/poetry.lock new file mode 100644 index 00000000..b9706f23 --- /dev/null +++ b/projects/web_api/poetry.lock @@ -0,0 +1,687 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alembic" +version = "1.13.2" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, + {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "aniso8601" +version = "9.0.1" +description = "A library for parsing ISO 8601 strings." +optional = false +python-versions = "*" +files = [ + {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, + {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, +] + +[package.extras] +dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] + +[[package]] +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +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]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-cors" +version = "5.0.0" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +files = [ + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, +] + +[package.dependencies] +Flask = ">=0.9" + +[[package]] +name = "flask-jwt-extended" +version = "4.6.0" +description = "Extended JWT integration with Flask" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "Flask-JWT-Extended-4.6.0.tar.gz", hash = "sha256:9215d05a9413d3855764bcd67035e75819d23af2fafb6b55197eb5a3313fdfb2"}, + {file = "Flask_JWT_Extended-4.6.0-py2.py3-none-any.whl", hash = "sha256:63a28fc9731bcc6c4b8815b6f954b5904caa534fc2ae9b93b1d3ef12930dca95"}, +] + +[package.dependencies] +Flask = ">=2.0,<4.0" +PyJWT = ">=2.0,<3.0" +Werkzeug = ">=0.14" + +[package.extras] +asymmetric-crypto = ["cryptography (>=3.3.1)"] + +[[package]] +name = "flask-marshmallow" +version = "1.2.1" +description = "Flask + marshmallow for beautiful APIs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_marshmallow-1.2.1-py3-none-any.whl", hash = "sha256:10b5048ecfaa26f7c8d0aed7d81083164450e6be8e81c04b3d4a586b3f7b6678"}, + {file = "flask_marshmallow-1.2.1.tar.gz", hash = "sha256:00ee96399ed664963afff3b5d6ee518640b0f91dbc2aace2b5abcf32f40ef23a"}, +] + +[package.dependencies] +Flask = ">=2.2" +marshmallow = ">=3.0.0" + +[package.extras] +dev = ["flask-marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["Sphinx (==7.2.6)", "marshmallow-sqlalchemy (>=0.19.0)", "sphinx-issues (==4.0.0)"] +sqlalchemy = ["flask-sqlalchemy (>=3.0.0)", "marshmallow-sqlalchemy (>=0.29.0)"] +tests = ["flask-marshmallow[sqlalchemy]", "pytest"] + +[[package]] +name = "flask-migrate" +version = "4.0.7" +description = "SQLAlchemy database migrations for Flask applications using Alembic." +optional = false +python-versions = ">=3.6" +files = [ + {file = "Flask-Migrate-4.0.7.tar.gz", hash = "sha256:dff7dd25113c210b069af280ea713b883f3840c1e3455274745d7355778c8622"}, + {file = "Flask_Migrate-4.0.7-py3-none-any.whl", hash = "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617"}, +] + +[package.dependencies] +alembic = ">=1.9.0" +Flask = ">=0.9" +Flask-SQLAlchemy = ">=1.0" + +[[package]] +name = "flask-restful" +version = "0.3.10" +description = "Simple framework for creating REST APIs" +optional = false +python-versions = "*" +files = [ + {file = "Flask-RESTful-0.3.10.tar.gz", hash = "sha256:fe4af2ef0027df8f9b4f797aba20c5566801b6ade995ac63b588abf1a59cec37"}, + {file = "Flask_RESTful-0.3.10-py2.py3-none-any.whl", hash = "sha256:1cf93c535172f112e080b0d4503a8d15f93a48c88bdd36dd87269bdaf405051b"}, +] + +[package.dependencies] +aniso8601 = ">=0.82" +Flask = ">=0.8" +pytz = "*" +six = ">=1.3.0" + +[package.extras] +docs = ["sphinx"] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"}, + {file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=2.0.16" + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "loguru" +version = "0.7.2" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, +] + +[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 (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] + +[[package]] +name = "mako" +version = "1.3.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "marshmallow" +version = "3.22.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"}, + {file = "marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "marshmallow-sqlalchemy" +version = "1.1.0" +description = "SQLAlchemy integration with the marshmallow (de)serialization library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow_sqlalchemy-1.1.0-py3-none-any.whl", hash = "sha256:cce261148e4c6ec4ee275f3d29352933380a1afa2fd3933f5e9ecd02fdc16ade"}, + {file = "marshmallow_sqlalchemy-1.1.0.tar.gz", hash = "sha256:2ab092da269dafa8a05d51a58409af71a8d2183958ba47143127dd239e0359d8"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0" +SQLAlchemy = ">=1.4.40,<3.0" + +[package.extras] +dev = ["marshmallow-sqlalchemy[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==1.0.0)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)"] +tests = ["pytest (<9)", "pytest-lazy-fixtures"] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pyjwt" +version = "2.9.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[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 = "sqlalchemy" +version = "2.0.32" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, + {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, + {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "werkzeug" +version = "3.0.4" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +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)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "dda1a445d3e4ac500b0e776fae43a153624d8d62cb91ca0f2584837b622a42a7" diff --git a/projects/web_api/pyproject.toml b/projects/web_api/pyproject.toml new file mode 100644 index 00000000..05164cd5 --- /dev/null +++ b/projects/web_api/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "web-api" +version = "0.1.0" +description = "" +authors = ["houlinfeng "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +flask = "^3.0.3" +flask-restful = "^0.3.10" +flask-cors = "^5.0.0" +flask-sqlalchemy = "^3.1.1" +flask-migrate = "^4.0.7" +flask-jwt-extended = "^4.6.0" +flask-marshmallow = "^1.2.1" +pyyaml = "^6.0.2" +loguru = "^0.7.2" +marshmallow-sqlalchemy = "^1.1.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/projects/web_api/tests/__init__.py b/projects/web_api/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/web_api/web_api/__init__.py b/projects/web_api/web_api/__init__.py new file mode 100644 index 00000000..8c0262ef --- /dev/null +++ b/projects/web_api/web_api/__init__.py @@ -0,0 +1 @@ +__all__ = ["common", "api"] \ No newline at end of file diff --git a/projects/web_api/web_api/api/__init__.py b/projects/web_api/web_api/api/__init__.py new file mode 100644 index 00000000..d1ce80af --- /dev/null +++ b/projects/web_api/web_api/api/__init__.py @@ -0,0 +1,36 @@ +import os +from .extentions import app, db, migrate, jwt, ma +from common.web_hook import before_request +from common.logger import setup_log + +root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +print("root_dir", root_dir) + +def _register_db(flask_app): + from common import import_models + db.init_app(flask_app) + with app.app_context(): + db.create_all() + + +def create_app(config): + """ + Create and configure an instance of the Flask application + :param config: + :return: + """ + app.static_folder = os.path.join(root_dir, "static") + if config is None: + config = {} + app.config.update(config) + setup_log(config) + _register_db(app) + migrate.init_app(app=app, db=db) + jwt.init_app(app=app) + ma.init_app(app=app) + from .analysis import analysis_blue + app.register_blueprint(analysis_blue) + + app.before_request(before_request) + + return app diff --git a/projects/web_api/web_api/api/analysis/__init__.py b/projects/web_api/web_api/api/analysis/__init__.py new file mode 100644 index 00000000..39dfbc04 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/__init__.py @@ -0,0 +1,18 @@ +from flask import Blueprint +from ..extentions import Api +from .upload_view import UploadPdfView +from .analysis_view import AnalysisTaskView, AnalysisTaskProgressView +from .img_md_view import ImgView, MdView +from .task_view import TaskView, HistoricalTasksView, DeleteTaskView + +analysis_blue = Blueprint('analysis', __name__) + +api_v2 = Api(analysis_blue, prefix='/api/v2') +api_v2.add_resource(UploadPdfView, '/analysis/upload_pdf') +api_v2.add_resource(AnalysisTaskView, '/extract/task/submit') +api_v2.add_resource(AnalysisTaskProgressView, '/extract/task/progress') +api_v2.add_resource(ImgView, '/analysis/pdf_img') +api_v2.add_resource(MdView, '/analysis/pdf_md') +api_v2.add_resource(TaskView, '/extract/taskQueue') +api_v2.add_resource(HistoricalTasksView, '/extract/list') +api_v2.add_resource(DeleteTaskView, '/extract/task') \ No newline at end of file diff --git a/projects/web_api/web_api/api/analysis/analysis_view.py b/projects/web_api/web_api/api/analysis/analysis_view.py new file mode 100644 index 00000000..da6560d2 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/analysis_view.py @@ -0,0 +1,231 @@ +import json +import threading +from pathlib import Path +from flask import request, current_app, url_for +from flask_restful import Resource +from .ext import find_file, task_state_map +# from .formula_ext import formula_detection, formula_recognition +from .serialization import AnalysisViewSchema +from marshmallow import ValidationError +from ..extentions import db +from .models import AnalysisTask, AnalysisPdf +from .pdf_ext import analysis_pdf_task +from common.custom_response import generate_response + + +class AnalysisTaskProgressView(Resource): + + def get(self): + """ + 获取任务进度 + :return: + """ + params = request.args + id = params.get('id') + analysis_task = AnalysisTask.query.filter(AnalysisTask.id == id).first() + if not analysis_task: + return generate_response(code=400, msg="Invalid ID", msgZH="无效id") + match analysis_task.task_type: + case 'pdf': + analysis_pdf = AnalysisPdf.query.filter(AnalysisPdf.id == analysis_task.analysis_pdf_id).first() + file_url = url_for('analysis.uploadpdfview', filename=analysis_task.file_name, as_attachment=False) + if analysis_task.status == 0: + data = { + "state": task_state_map.get(analysis_task.status), + "status": analysis_pdf.status, + "url": file_url, + "fileName": analysis_task.file_name, + "content": [], + "markdownUrl": [], + "fullMdLink": "", + "type": analysis_task.task_type, + } + return generate_response(data=data) + elif analysis_task.status == 1: + if analysis_pdf.status == 1: # 任务正常完成 + bbox_info = json.loads(analysis_pdf.bbox_info) + md_link_list = json.loads(analysis_pdf.md_link_list) + full_md_link = analysis_pdf.full_md_link + data = { + "state": task_state_map.get(analysis_task.status), + "status": analysis_pdf.status, + "url": file_url, + "fileName": analysis_task.file_name, + "content": bbox_info, + "markdownUrl": md_link_list, + "fullMdLink": full_md_link, + "type": analysis_task.task_type, + } + return generate_response(data=data) + else: # 任务异常结束 + data = { + "state": task_state_map.get(analysis_task.status), + "status": analysis_pdf.status, + "url": file_url, + "fileName": analysis_task.file_name, + "content": [], + "markdownUrl": [], + "fullMdLink": "", + "type": analysis_task.task_type, + } + return generate_response(code=-60004, data=data, msg="Failed to retrieve PDF parsing progress", + msgZh="无法获取PDF解析进度") + else: + data = { + "state": task_state_map.get(analysis_task.status), + "status": analysis_pdf.status, + "url": file_url, + "fileName": analysis_task.file_name, + "content": [], + "markdownUrl": [], + "fullMdLink": "", + "type": analysis_task.task_type, + } + return generate_response(data=data) + case 'formula-detect': + pass + case 'formula-extract': + pass + case 'table-recogn': + return generate_response(code=400, msg="Not yet supported", msgZH="尚不支持") + case _: + return generate_response() + + +class AnalysisTaskView(Resource): + + def post(self): + """ + 提交任务 + :return: + """ + analysis_view_schema = AnalysisViewSchema() + try: + params = analysis_view_schema.load(request.get_json()) + except ValidationError as err: + return generate_response(code=400, msg=err.messages) + file_key = params.get("fileKey") + file_name = params.get("fileName") + task_type = params.get("taskType") + is_ocr = params.get("isOcr", False) + + pdf_upload_folder = current_app.config['PDF_UPLOAD_FOLDER'] + upload_dir = f"{current_app.static_folder}/{pdf_upload_folder}" + file_path = find_file(file_key, upload_dir) + match task_type: + case 'pdf': + if not file_path: + return generate_response(code=400, msg="FileKey is invalid, no PDF file found", + msgZH="fileKey无效,未找到pdf文件") + analysis_task = AnalysisTask.query.filter(AnalysisTask.status.in_([0, 2])).first() + file_name = Path(file_path).name + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf( + file_name=file_name, + file_path=file_path, + status=3 if analysis_task else 0, + ) + db.session.add(analysis_pdf_object) + db.session.flush() + analysis_pdf_id = analysis_pdf_object.id + with db.auto_commit(): + analysis_task_object = AnalysisTask( + file_key=file_key, + file_name=file_name, + task_type=task_type, + is_ocr=is_ocr, + status=2 if analysis_task else 0, + analysis_pdf_id=analysis_pdf_id + ) + db.session.add(analysis_task_object) + db.session.flush() + analysis_task_id = analysis_task_object.id + if not analysis_task: # 已有同类型任务在执行,请等待执行完成 + file_stem = Path(file_path).stem + pdf_analysis_folder = current_app.config['PDF_ANALYSIS_FOLDER'] + pdf_dir = f"{current_app.static_folder}/{pdf_analysis_folder}/{file_stem}" + image_dir = f"{pdf_dir}/images" + t = threading.Thread(target=analysis_pdf_task, + args=(pdf_dir, image_dir, file_path, is_ocr, analysis_pdf_id)) + t.start() + # 生成文件的URL路径 + file_url = url_for('analysis.uploadpdfview', filename=file_name, as_attachment=False) + data = { + "url": file_url, + "fileName": file_name, + "id": analysis_task_id + } + return generate_response(data=data) + case 'formula-detect': + # if not file_path: + # return generate_response(code=400, msg="FileKey is invalid, no image file found", + # msgZH="fileKey无效,未找到图片") + # return formula_detection(file_path, upload_dir) + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case 'formula-extract': + # if not file_path: + # return generate_response(code=400, msg="FileKey is invalid, no image file found", + # msgZH="fileKey无效,未找到图片") + # return formula_recognition(file_path, upload_dir) + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case 'table-recogn': + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case _: + return generate_response(code=400, msg="Not yet supported", msgZH="参数不支持") + + def put(self): + """ + 重新发起任务 + :return: + """ + params = json.loads(request.data) + id = params.get('id') + analysis_task = AnalysisTask.query.filter(AnalysisTask.id == id).first() + match analysis_task.task_type: + case 'pdf': + task_r_p = AnalysisTask.query.filter(AnalysisTask.status.in_([0, 2])).first() + if task_r_p: + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf.query.filter_by(id=analysis_task.analysis_pdf_id).first() + analysis_pdf_object.status = 3 + db.session.add(analysis_pdf_object) + with db.auto_commit(): + analysis_task.status = 2 + db.session.add(analysis_task) + else: + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf.query.filter_by(id=analysis_task.analysis_pdf_id).first() + analysis_pdf_object.status = 0 + db.session.add(analysis_pdf_object) + with db.auto_commit(): + analysis_task.status = 0 + db.session.add(analysis_task) + + pdf_upload_folder = current_app.config['PDF_UPLOAD_FOLDER'] + upload_dir = f"{current_app.static_folder}/{pdf_upload_folder}" + file_path = find_file(analysis_task.file_key, upload_dir) + file_stem = Path(file_path).stem + pdf_analysis_folder = current_app.config['PDF_ANALYSIS_FOLDER'] + pdf_dir = f"{current_app.static_folder}/{pdf_analysis_folder}/{file_stem}" + image_dir = f"{pdf_dir}/images" + t = threading.Thread(target=analysis_pdf_task, + args=(pdf_dir, image_dir, file_path, analysis_task.is_ocr, + analysis_task.analysis_pdf_id)) + t.start() + + # 生成文件的URL路径 + file_url = url_for('analysis.uploadpdfview', filename=analysis_task.file_name, as_attachment=False) + data = { + "url": file_url, + "fileName": analysis_task.file_name, + "id": analysis_task.id + } + return generate_response(data=data) + case 'formula-detect': + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case 'formula-extract': + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case 'table-recogn': + return generate_response(code=400, msg="Not yet supported", msgZH="功能待开发") + case _: + return generate_response(code=400, msg="Not yet supported", msgZH="参数不支持") diff --git a/projects/web_api/web_api/api/analysis/ext.py b/projects/web_api/web_api/api/analysis/ext.py new file mode 100644 index 00000000..9549a87e --- /dev/null +++ b/projects/web_api/web_api/api/analysis/ext.py @@ -0,0 +1,25 @@ +import os + +task_state_map = { + 0: "running", + 1: "finished", + 2: "pending", +} + + +def find_file(file_key, file_dir): + """ + 查询文件 + :param file_key: 文件哈希 + :param file_dir: 文件目录 + :return: + """ + pdf_path = "" + for root, subDirs, files in os.walk(file_dir): + for fileName in files: + if fileName.startswith(file_key): + pdf_path = os.path.join(root, fileName) + break + if pdf_path: + break + return pdf_path diff --git a/projects/web_api/web_api/api/analysis/formula_ext.py b/projects/web_api/web_api/api/analysis/formula_ext.py new file mode 100644 index 00000000..aebcca14 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/formula_ext.py @@ -0,0 +1,280 @@ +import os +import pkgutil +import numpy as np +import yaml +import argparse +import cv2 +from pathlib import Path +from ultralytics import YOLO +from unimernet.common.config import Config +import unimernet.tasks as tasks +from unimernet.processors import load_processor +from magic_pdf.libs.config_reader import get_local_models_dir, get_device +from torchvision import transforms +from magic_pdf.pre_proc.ocr_span_list_modify import remove_overlaps_low_confidence_spans, remove_overlaps_min_spans +from PIL import Image +from common.ext import singleton_func +from common.custom_response import generate_response + + +def mfd_model_init(weight): + mfd_model = YOLO(weight) + return mfd_model + + +def mfr_model_init(weight_dir, cfg_path, _device_='cpu'): + args = argparse.Namespace(cfg_path=cfg_path, options=None) + cfg = Config(args) + cfg.config.model.pretrained = os.path.join(weight_dir, "pytorch_model.bin") + cfg.config.model.model_config.model_name = weight_dir + cfg.config.model.tokenizer_config.path = weight_dir + task = tasks.setup_task(cfg) + model = task.build_model(cfg) + model = model.to(_device_) + vis_processor = load_processor('formula_image_eval', cfg.config.datasets.formula_rec_eval.vis_processor.eval) + return model, vis_processor + + +@singleton_func +class CustomPEKModel: + def __init__(self): + # PDF-Extract-Kit/models + models_dir = get_local_models_dir() + self.device = get_device() + loader = pkgutil.get_loader("magic_pdf") + root_dir = Path(loader.path).parent + # model_config目录 + model_config_dir = os.path.join(root_dir, 'resources', 'model_config') + # 构建 model_configs.yaml 文件的完整路径 + config_path = os.path.join(model_config_dir, 'model_configs.yaml') + with open(config_path, "r", encoding='utf-8') as f: + configs = yaml.load(f, Loader=yaml.FullLoader) + + # 初始化公式检测模型 + self.mfd_model = mfd_model_init(str(os.path.join(models_dir, configs["weights"]["mfd"]))) + + # 初始化公式解析模型 + mfr_weight_dir = str(os.path.join(models_dir, configs["weights"]["mfr"])) + mfr_cfg_path = str(os.path.join(model_config_dir, "UniMERNet", "demo.yaml")) + self.mfr_model, mfr_vis_processors = mfr_model_init(mfr_weight_dir, mfr_cfg_path, _device_=self.device) + self.mfr_transform = transforms.Compose([mfr_vis_processors, ]) + + +def get_all_spans(layout_dets) -> list: + def remove_duplicate_spans(spans): + new_spans = [] + for span in spans: + if not any(span == existing_span for existing_span in new_spans): + new_spans.append(span) + return new_spans + + all_spans = [] + # allow_category_id_list = [3, 5, 13, 14, 15] + """当成span拼接的""" + # 3: 'image', # 图片 + # 5: 'table', # 表格 + # 13: 'inline_equation', # 行内公式 + # 14: 'interline_equation', # 行间公式 + # 15: 'text', # ocr识别文本 + for layout_det in layout_dets: + if layout_det.get("bbox") is not None: + # 兼容直接输出bbox的模型数据,如paddle + x0, y0, x1, y1 = layout_det["bbox"] + else: + # 兼容直接输出poly的模型数据,如xxx + x0, y0, _, _, x1, y1, _, _ = layout_det["poly"] + bbox = [x0, y0, x1, y1] + layout_det["bbox"] = bbox + all_spans.append(layout_det) + return remove_duplicate_spans(all_spans) + + +def formula_predict(mfd_model, image): + """ + 公式检测 + :param mfd_model: + :param image: + :return: + """ + latex_filling_list = [] + # 公式检测 + mfd_res = mfd_model.predict(image, imgsz=1888, conf=0.25, iou=0.45, verbose=True)[0] + for xyxy, conf, cla in zip(mfd_res.boxes.xyxy.cpu(), mfd_res.boxes.conf.cpu(), mfd_res.boxes.cls.cpu()): + xmin, ymin, xmax, ymax = [int(p.item()) for p in xyxy] + new_item = { + 'category_id': 13 + int(cla.item()), + 'poly': [xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax], + 'score': round(float(conf.item()), 2), + 'latex': '', + } + latex_filling_list.append(new_item) + return latex_filling_list + + +def formula_detection(file_path, upload_dir): + """ + 公式检测 + :param file_path: 文件路径 + :param upload_dir: 上传文件夹 + :return: + """ + try: + image_open = Image.open(file_path) + except IOError: + return generate_response(code=400, msg="params is not valid", msgZh="参数类型不是图片,无效参数") + + filename = Path(file_path).name + + # 获取图片宽高 + width, height = image_open.size + # 转换为RGB,忽略透明度通道 + rgb_image = image_open.convert('RGB') + # 保存转换后的图片 + rgb_image.save(file_path) + + # 初始化模型 + cpm = CustomPEKModel() + # 初始化公式检测模型 + mfd_model = cpm.mfd_model + + image_conv = Image.open(file_path) + image_array = np.array(image_conv) + pdf_width = 1416 + pdf_height = 1888 + + # 重置图片大小 + scale = min(pdf_width // 2 / width, pdf_height // 2 / height) # 缩放比例 + nw = int(width * scale) + nh = int(height * scale) + image_resize = cv2.resize(image_array, (nw, nh), interpolation=cv2.INTER_LINEAR) + resize_image_path = f"{upload_dir}/resize_{filename}" + cv2.imwrite(resize_image_path, image_resize) + # 将重置的图片贴到pdf白纸中 + x = (pdf_width - nw) // 2 + y = (pdf_height - nh) // 2 + new_img = Image.new('RGB', (pdf_width, pdf_height), 'white') + image_scale = Image.open(resize_image_path) + new_img.paste(image_scale, (x, y)) + + # 公式检测 + latex_filling_list = formula_predict(mfd_model, new_img) + + os.remove(resize_image_path) + + # 将缩放图公式检测的坐标还原为原图公式检测的坐标 + for item in latex_filling_list: + item_poly = item["poly"] + item["poly"] = [ + (item_poly[0] - x) / scale, + (item_poly[1] - y) / scale, + (item_poly[2] - x) / scale, + (item_poly[3] - y) / scale, + (item_poly[4] - x) / scale, + (item_poly[5] - y) / scale, + (item_poly[6] - x) / scale, + (item_poly[7] - y) / scale, + ] + + if not latex_filling_list: + return generate_response(code=1001, msg="detection fail", msgZh="公式检测失败,图片过小,无法检测") + + spans = get_all_spans(latex_filling_list) + '''删除重叠spans中置信度较低的那些''' + spans, dropped_spans_by_confidence = remove_overlaps_low_confidence_spans(spans) + '''删除重叠spans中较小的那些''' + spans, dropped_spans_by_span_overlap = remove_overlaps_min_spans(spans) + + return generate_response(data={ + 'layout': spans, + }) + + +def formula_recognition(file_path, upload_dir): + """ + 公式识别 + :param file_path: 文件路径 + :param upload_dir: 上传文件夹 + :return: + """ + try: + image_open = Image.open(file_path) + except IOError: + return generate_response(code=400, msg="params is not valid", msgZh="参数类型不是图片,无效参数") + + filename = Path(file_path).name + + # 获取图片宽高 + width, height = image_open.size + # 转换为RGB,忽略透明度通道 + rgb_image = image_open.convert('RGB') + # 保存转换后的图片 + rgb_image.save(file_path) + + image_conv = Image.open(file_path) + image_array = np.array(image_conv) + pdf_width = 1416 + pdf_height = 1888 + + # 重置图片大小 + scale = min(pdf_width // 2 / width, pdf_height // 2 / height) # 缩放比例 + nw = int(width * scale) + nh = int(height * scale) + image_resize = cv2.resize(image_array, (nw, nh), interpolation=cv2.INTER_LINEAR) + resize_image_path = f"{upload_dir}/resize_{filename}" + cv2.imwrite(resize_image_path, image_resize) + # 将重置的图片贴到pdf白纸中 + x = (pdf_width - nw) // 2 + y = (pdf_height - nh) // 2 + new_img = Image.new('RGB', (pdf_width, pdf_height), 'white') + image_scale = Image.open(resize_image_path) + new_img.paste(image_scale, (x, y)) + new_img_array = np.array(new_img) + + # 初始化模型 + cpm = CustomPEKModel() + # device + device = cpm.device + # 初始化公式检测模型 + mfd_model = cpm.mfd_model + # 初始化公式解析模型 + mfr_model = cpm.mfr_model + mfr_transform = cpm.mfr_transform + # 公式识别 + latex_filling_list, mfr_res = formula_recognition(mfd_model, new_img_array, mfr_transform, device, mfr_model, + image_open) + + os.remove(resize_image_path) + + # 将缩放图公式检测的坐标还原为原图公式检测的坐标 + for item in latex_filling_list: + item_poly = item["poly"] + item["poly"] = [ + (item_poly[0] - x) / scale, + (item_poly[1] - y) / scale, + (item_poly[2] - x) / scale, + (item_poly[3] - y) / scale, + (item_poly[4] - x) / scale, + (item_poly[5] - y) / scale, + (item_poly[6] - x) / scale, + (item_poly[7] - y) / scale, + ] + + spans = get_all_spans(latex_filling_list) + '''删除重叠spans中置信度较低的那些''' + spans, dropped_spans_by_confidence = remove_overlaps_low_confidence_spans(spans) + '''删除重叠spans中较小的那些''' + spans, dropped_spans_by_span_overlap = remove_overlaps_min_spans(spans) + + if not latex_filling_list: + width, height = image_open.size + latex_filling_list.append({ + 'category_id': 14, + 'poly': [0, 0, width, 0, width, height, 0, height], + 'score': 1, + 'latex': mfr_res[0] if mfr_res else "", + }) + + return generate_response(data={ + 'layout': spans if spans else latex_filling_list, + "mfr_res": mfr_res + }) diff --git a/projects/web_api/web_api/api/analysis/img_md_view.py b/projects/web_api/web_api/api/analysis/img_md_view.py new file mode 100644 index 00000000..66df7bc7 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/img_md_view.py @@ -0,0 +1,46 @@ +from pathlib import Path +from flask import request, current_app, send_from_directory +from flask_restful import Resource + + +class ImgView(Resource): + def get(self): + """ + 获取pdf解析的图片 + :return: + """ + params = request.args + pdf = params.get('pdf') + filename = params.get('filename') + as_attachment = params.get('as_attachment') + if str(as_attachment).lower() == "true": + as_attachment = True + else: + as_attachment = False + file_stem = Path(pdf).stem + pdf_analysis_folder = current_app.config['PDF_ANALYSIS_FOLDER'] + pdf_dir = f"{current_app.static_folder}/{pdf_analysis_folder}/{file_stem}" + image_dir = f"{pdf_dir}/images" + response = send_from_directory(image_dir, filename, as_attachment=as_attachment) + return response + + +class MdView(Resource): + def get(self): + """ + 获取pdf解析的markdown + :return: + """ + params = request.args + pdf = params.get('pdf') + filename = params.get('filename') + as_attachment = params.get('as_attachment') + if str(as_attachment).lower() == "true": + as_attachment = True + else: + as_attachment = False + file_stem = Path(pdf).stem + pdf_analysis_folder = current_app.config['PDF_ANALYSIS_FOLDER'] + pdf_dir = f"{current_app.static_folder}/{pdf_analysis_folder}/{file_stem}" + response = send_from_directory(pdf_dir, filename, as_attachment=as_attachment) + return response diff --git a/projects/web_api/web_api/api/analysis/models.py b/projects/web_api/web_api/api/analysis/models.py new file mode 100644 index 00000000..23b8b1d2 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/models.py @@ -0,0 +1,29 @@ +from datetime import datetime +from ..extentions import db + + +class AnalysisTask(db.Model): + __tablename__ = 'analysis_task' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + file_key = db.Column(db.Text, comment="文件唯一哈希") + file_name = db.Column(db.Text, comment="文件名称") + task_type = db.Column(db.String(128), comment="任务类型") + is_ocr = db.Column(db.Boolean, default=False, comment="是否ocr") + status = db.Column(db.Integer, default=0, comment="状态") # 0 running 1 finished 2 pending + analysis_pdf_id = db.Column(db.Integer, comment="analysis_pdf的id") + create_date = db.Column(db.DateTime(), nullable=False, default=datetime.now) + update_date = db.Column(db.DateTime(), nullable=False, default=datetime.now, onupdate=datetime.now) + + +class AnalysisPdf(db.Model): + __tablename__ = 'analysis_pdf' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + file_name = db.Column(db.Text, comment="文件名称") + file_url = db.Column(db.Text, comment="文件原路径") + file_path = db.Column(db.Text, comment="文件路径") + status = db.Column(db.Integer, default=3, comment="状态") # 0 转换中 1 已完成 2 转换失败 3 init + bbox_info = db.Column(db.Text, comment="坐标数据") + md_link_list = db.Column(db.Text, comment="markdown分页链接") + full_md_link = db.Column(db.Text, comment="markdown全文链接") + create_date = db.Column(db.DateTime(), nullable=False, default=datetime.now) + update_date = db.Column(db.DateTime(), nullable=False, default=datetime.now, onupdate=datetime.now) \ No newline at end of file diff --git a/projects/web_api/web_api/api/analysis/pdf_ext.py b/projects/web_api/web_api/api/analysis/pdf_ext.py new file mode 100644 index 00000000..800d9510 --- /dev/null +++ b/projects/web_api/web_api/api/analysis/pdf_ext.py @@ -0,0 +1,162 @@ +import json +import re +import traceback +from pathlib import Path +from flask import current_app, url_for +from magic_pdf.rw.DiskReaderWriter import DiskReaderWriter +from magic_pdf.pipe.UNIPipe import UNIPipe +import magic_pdf.model as model_config +from magic_pdf.libs.json_compressor import JsonCompressor +from magic_pdf.dict2md.ocr_mkcontent import ocr_mk_mm_markdown_with_para_and_pagination +from .ext import find_file +from ..extentions import app, db +from .models import AnalysisPdf, AnalysisTask +from common.error_types import ApiException +from loguru import logger + +model_config.__use_inside_model__ = True + + +def analysis_pdf(image_dir, pdf_bytes, is_ocr=False): + try: + model_json = [] # model_json传空list使用内置模型解析 + logger.info(f"is_ocr: {is_ocr}") + if not is_ocr: + jso_useful_key = {"_pdf_type": "", "model_list": model_json} + image_writer = DiskReaderWriter(image_dir) + pipe = UNIPipe(pdf_bytes, jso_useful_key, image_writer, is_debug=True) + pipe.pipe_classify() + else: + jso_useful_key = {"_pdf_type": "ocr", "model_list": model_json} + image_writer = DiskReaderWriter(image_dir) + pipe = UNIPipe(pdf_bytes, jso_useful_key, image_writer, is_debug=True) + """如果没有传入有效的模型数据,则使用内置model解析""" + if len(model_json) == 0: + if model_config.__use_inside_model__: + pipe.pipe_analyze() + else: + logger.error("need model list input") + exit(1) + pipe.pipe_parse() + pdf_mid_data = JsonCompressor.decompress_json(pipe.get_compress_pdf_mid_data()) + pdf_info_list = pdf_mid_data["pdf_info"] + md_content = json.dumps(ocr_mk_mm_markdown_with_para_and_pagination(pdf_info_list, image_dir), + ensure_ascii=False) + bbox_info = get_bbox_info(pdf_info_list) + return md_content, bbox_info + except Exception as e: + logger.error(traceback.format_exc()) + + +def get_bbox_info(data): + bbox_info = [] + for page in data: + preproc_blocks = page.get("preproc_blocks", []) + discarded_blocks = page.get("discarded_blocks", []) + bbox_info.append({ + "preproc_blocks": preproc_blocks, + "page_idx": page.get("page_idx"), + "page_size": page.get("page_size"), + "discarded_blocks": discarded_blocks, + }) + return bbox_info + + +def analysis_pdf_task(pdf_dir, image_dir, pdf_path, is_ocr, analysis_pdf_id): + """ + 解析pdf + :param pdf_dir: pdf解析目录 + :param image_dir: 图片目录 + :param pdf_path: pdf路径 + :param is_ocr: 是否启用ocr + :param analysis_pdf_id: pdf解析表id + :return: + """ + try: + logger.info(f"start task: {pdf_path}") + logger.info(f"image_dir: {image_dir}") + if not Path(image_dir).exists(): + Path(image_dir).mkdir(parents=True, exist_ok=True) + with open(pdf_path, 'rb') as file: + pdf_bytes = file.read() + md_content, bbox_info = analysis_pdf(image_dir, pdf_bytes, is_ocr) + img_list = Path(image_dir).glob('*') if Path(image_dir).exists() else [] + + pdf_name = Path(pdf_path).name + with app.app_context(): + for img in img_list: + img_name = Path(img).name + regex = re.compile(fr'.*\((.*?{img_name})') + regex_result = regex.search(md_content) + img_url = url_for('analysis.imgview', filename=img_name, as_attachment=False) + md_content = md_content.replace(regex_result.group(1), f"{img_url}&pdf={pdf_name}") + + full_md_content = "" + for item in json.loads(md_content): + full_md_content += item["md_content"] + "\n" + + full_md_name = "full.md" + with open(f"{pdf_dir}/{full_md_name}", "w") as file: + file.write(full_md_content) + with app.app_context(): + full_md_link = url_for('analysis.mdview', filename=full_md_name, as_attachment=False) + full_md_link = f"{full_md_link}&pdf={pdf_name}" + + md_link_list = [] + with app.app_context(): + for n, md in enumerate(json.loads(md_content)): + md_content = md["md_content"] + md_name = f"{md.get('page_no', n)}.md" + with open(f"{pdf_dir}/{md_name}", "w") as file: + file.write(md_content) + md_url = url_for('analysis.mdview', filename=md_name, as_attachment=False) + md_link_list.append(f"{md_url}&pdf={pdf_name}") + + with app.app_context(): + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf.query.filter_by(id=analysis_pdf_id).first() + analysis_pdf_object.status = 1 + analysis_pdf_object.bbox_info = json.dumps(bbox_info, ensure_ascii=False) + analysis_pdf_object.md_link_list = json.dumps(md_link_list, ensure_ascii=False) + analysis_pdf_object.full_md_link = full_md_link + db.session.add(analysis_pdf_object) + with db.auto_commit(): + analysis_task_object = AnalysisTask.query.filter_by(analysis_pdf_id=analysis_pdf_id).first() + analysis_task_object.status = 1 + db.session.add(analysis_task_object) + logger.info(f"finished!") + except Exception as e: + logger.error(traceback.format_exc()) + with app.app_context(): + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf.query.filter_by(id=analysis_pdf_id).first() + analysis_pdf_object.status = 2 + db.session.add(analysis_pdf_object) + with db.auto_commit(): + analysis_task_object = AnalysisTask.query.filter_by(analysis_pdf_id=analysis_pdf_id).first() + analysis_task_object.status = 1 + db.session.add(analysis_task_object) + raise ApiException(code=500, msg="PDF parsing failed", msgZH="pdf解析失败") + finally: + # 执行pending + with app.app_context(): + analysis_task_object = AnalysisTask.query.filter_by(status=2).order_by( + AnalysisTask.update_date.asc()).first() + if analysis_task_object: + pdf_upload_folder = current_app.config['PDF_UPLOAD_FOLDER'] + upload_dir = f"{current_app.static_folder}/{pdf_upload_folder}" + file_path = find_file(analysis_task_object.file_key, upload_dir) + file_stem = Path(file_path).stem + pdf_analysis_folder = current_app.config['PDF_ANALYSIS_FOLDER'] + pdf_dir = f"{current_app.static_folder}/{pdf_analysis_folder}/{file_stem}" + image_dir = f"{pdf_dir}/images" + with db.auto_commit(): + analysis_pdf_object = AnalysisPdf.query.filter_by(id=analysis_task_object.analysis_pdf_id).first() + analysis_pdf_object.status = 0 + db.session.add(analysis_pdf_object) + with db.auto_commit(): + analysis_task_object.status = 0 + db.session.add(analysis_task_object) + analysis_pdf_task(pdf_dir, image_dir, file_path, analysis_task_object.is_ocr, analysis_task_object.analysis_pdf_id) + else: + logger.info(f"all task finished!") diff --git a/projects/web_api/web_api/api/analysis/serialization.py b/projects/web_api/web_api/api/analysis/serialization.py new file mode 100644 index 00000000..727c6aed --- /dev/null +++ b/projects/web_api/web_api/api/analysis/serialization.py @@ -0,0 +1,28 @@ +from marshmallow import Schema, fields, validates_schema, validates +from common.error_types import ApiException +from .models import AnalysisTask + + +class BooleanField(fields.Boolean): + def _deserialize(self, value, attr, data, **kwargs): + # 进行自定义验证 + if not isinstance(value, bool): + raise ApiException(code=400, msg="isOcr not a valid boolean", msgZH="isOcr不是有效的布尔值") + + return value + + +class AnalysisViewSchema(Schema): + fileKey = fields.Str(required=True) + fileName = fields.Str() + taskType = fields.Str(required=True) + isOcr = BooleanField() + + @validates_schema(pass_many=True) + def validate_passwords(self, data, **kwargs): + task_type = data['taskType'] + file_key = data['fileKey'] + if not file_key: + raise ApiException(code=400, msg="fileKey cannot be empty", msgZH="fileKey不能为空") + if not task_type: + raise ApiException(code=400, msg="taskType cannot be empty", msgZH="taskType不能为空") diff --git a/projects/web_api/web_api/api/analysis/task_view.py b/projects/web_api/web_api/api/analysis/task_view.py new file mode 100644 index 00000000..ac826acf --- /dev/null +++ b/projects/web_api/web_api/api/analysis/task_view.py @@ -0,0 +1,95 @@ +import json +from flask import url_for, request +from flask_restful import Resource +from sqlalchemy import func +from ..extentions import db +from .models import AnalysisTask, AnalysisPdf +from .ext import task_state_map +from common.custom_response import generate_response + + +class TaskView(Resource): + def get(self): + """ + 查询正在进行的任务 + :return: + """ + analysis_task_running = AnalysisTask.query.filter(AnalysisTask.status == 0).first() + analysis_task_pending = AnalysisTask.query.filter(AnalysisTask.status == 2).order_by( + AnalysisTask.create_date.asc()).all() + pending_total = db.session.query(func.count(AnalysisTask.id)).filter(AnalysisTask.status == 2).scalar() + task_nums = pending_total + 1 + data = [ + { + "queues": task_nums, # 正在排队的任务总数 + "rank": 1, + "id": analysis_task_running.id, + "url": url_for('analysis.uploadpdfview', filename=analysis_task_running.file_name, as_attachment=False), + "fileName": analysis_task_running.file_name, + "type": analysis_task_running.task_type, + "state": task_state_map.get(analysis_task_running.status), + } + ] + for n, task in enumerate(analysis_task_pending): + data.append({ + "queues": task_nums, # 正在排队的任务总数 + "rank": n + 2, + "id": task.id, + "url": url_for('analysis.uploadpdfview', filename=task.file_name, as_attachment=False), + "fileName": task.file_name, + "type": task.task_type, + "state": task_state_map.get(task.status), + }) + data.reverse() + return generate_response(data=data, total=task_nums) + + +class HistoricalTasksView(Resource): + def get(self): + """ + 获取任务历史记录 + :return: + """ + params = request.args + page_no = params.get('pageNo', 1) + page_size = params.get('pageSize', 10) + total = db.session.query(func.count(AnalysisTask.id)).scalar() + analysis_task = AnalysisTask.query.order_by(AnalysisTask.create_date.desc()).paginate(page=int(page_no), + per_page=int(page_size), + error_out=False) + data = [] + for n, task in enumerate(analysis_task): + data.append({ + "fileName": task.file_name, + "id": task.id, + "type": task.task_type, + "state": task_state_map.get(task.status), + }) + data = { + "list": data, + "total": total, + "pageNo": page_no, + "pageSize": page_size, + } + return generate_response(data=data) + + +class DeleteTaskView(Resource): + def delete(self): + """ + 删除任务历史记录 + :return: + """ + params = json.loads(request.data) + id = params.get('id') + + analysis_task = AnalysisTask.query.filter(AnalysisTask.id == id, AnalysisTask.status != 0).first() + if analysis_task: + analysis_pdf = AnalysisPdf.query.filter(AnalysisPdf.id == AnalysisTask.analysis_pdf_id).first() + with db.auto_commit(): + db.session.delete(analysis_pdf) + db.session.delete(analysis_task) + else: + return generate_response(code=400, msg="The ID is incorrect", msgZH="id不正确") + + return generate_response(data={"id": id}) diff --git a/projects/web_api/web_api/api/analysis/upload_view.py b/projects/web_api/web_api/api/analysis/upload_view.py new file mode 100644 index 00000000..2032158d --- /dev/null +++ b/projects/web_api/web_api/api/analysis/upload_view.py @@ -0,0 +1,89 @@ +import json +import traceback +import requests +from flask import request, current_app, url_for, send_from_directory +from flask_restful import Resource +from werkzeug.utils import secure_filename +from pathlib import Path +from common.ext import is_pdf, calculate_file_hash, url_is_pdf +from io import BytesIO +from werkzeug.datastructures import FileStorage +from common.custom_response import generate_response +from loguru import logger + + +class UploadPdfView(Resource): + + def get(self): + """ + 获取pdf + :return: + """ + params = request.args + filename = params.get('filename') + as_attachment = params.get('as_attachment') + if str(as_attachment).lower() == "true": + as_attachment = True + else: + as_attachment = False + pdf_upload_folder = current_app.config['PDF_UPLOAD_FOLDER'] + response = send_from_directory(f"{current_app.static_folder}/{pdf_upload_folder}", filename, + as_attachment=as_attachment) + return response + + def post(self): + """ + 上传pdf + :return: + """ + file_list = request.files.getlist("file") + if file_list: + file = file_list[0] + filename = secure_filename(file.filename) + if not file or file and not is_pdf(filename, file): + return generate_response(code=400, msg="Invalid PDF file", msgZH="PDF文件参数无效") + else: + params = json.loads(request.data) + pdf_url = params.get('pdfUrl') + try: + response = requests.get(pdf_url, stream=True) + except ConnectionError as e: + logger.error(traceback.format_exc()) + return generate_response(code=400, msg="params is not valid", msgZh="参数错误,pdf链接无法访问") + if response.status_code != 200: + return generate_response(code=400, msg="params is not valid", msgZh="参数错误,pdf链接响应状态异常") + # 创建一个模拟的 FileStorage 对象 + file_content = BytesIO(response.content) + filename = Path(pdf_url).name if ".pdf" in pdf_url else f"{Path(pdf_url).name}.pdf" + file = FileStorage( + stream=file_content, + filename=filename, + content_type=response.headers.get('Content-Type', 'application/octet-stream') + ) + if not file or file and not url_is_pdf(file): + return generate_response(code=400, msg="Invalid PDF file", msgZH="PDF文件参数无效") + + pdf_upload_folder = current_app.config['PDF_UPLOAD_FOLDER'] + upload_dir = f"{current_app.static_folder}/{pdf_upload_folder}" + if not Path(upload_dir).exists(): + Path(upload_dir).mkdir(parents=True, exist_ok=True) + file_key = calculate_file_hash(file) + # new_filename = f"{int(time.time())}_{filename}" + new_filename = f"{file_key}_{filename}" + file_path = f"{upload_dir}/{new_filename}" + # file.save(file_path) + chunk_size = 8192 + with open(file_path, 'wb') as f: + while True: + chunk = file.stream.read(chunk_size) + if not chunk: + break + f.write(chunk) + + # 生成文件的URL路径 + file_url = url_for('analysis.uploadpdfview', filename=new_filename, as_attachment=False) + data = { + "url": file_url, + "file_key": file_key + } + return generate_response(data=data) diff --git a/projects/web_api/web_api/api/extentions.py b/projects/web_api/web_api/api/extentions.py new file mode 100644 index 00000000..643b582b --- /dev/null +++ b/projects/web_api/web_api/api/extentions.py @@ -0,0 +1,61 @@ +from flask import Flask, jsonify +from flask_restful import Api as _Api +from flask_cors import CORS +from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy +from flask_migrate import Migrate +from contextlib import contextmanager +from flask_jwt_extended import JWTManager +from flask_marshmallow import Marshmallow +from common.error_types import ApiException +from werkzeug.exceptions import HTTPException +from loguru import logger + + +class Api(_Api): + def handle_error(self, e): + if isinstance(e, ApiException): + code = e.code + msg = e.msg + msgZH = e.msgZH + error_code = e.error_code + elif isinstance(e, HTTPException): + code = e.code + msg = e.description + msgZH = "服务异常,详细信息请查看日志" + error_code = e.code + else: + code = 500 + msg = str(e) + error_code = 500 + msgZH = "服务异常,详细信息请查看日志" + + # 使用 loguru 记录异常信息 + logger.opt(exception=e).error(f"An error occurred: {msg}") + + return jsonify({ + "error": "Internal Server Error" if code == 500 else e.name, + "msg": msg, + "msgZH": msgZH, + "code": code, + "error_code": error_code + }), code + + +class SQLAlchemy(_SQLAlchemy): + @contextmanager + def auto_commit(self): + try: + yield + db.session.commit() + db.session.flush() + except Exception as e: + db.session.rollback() + raise e + + +app = Flask(__name__) +CORS(app, supports_credentials=True) +db = SQLAlchemy() +migrate = Migrate() +jwt = JWTManager() +ma = Marshmallow() diff --git a/projects/web_api/web_api/app.py b/projects/web_api/web_api/app.py new file mode 100644 index 00000000..d98c0e56 --- /dev/null +++ b/projects/web_api/web_api/app.py @@ -0,0 +1,54 @@ +import socket +from api import create_app +from pathlib import Path +import yaml + + +def get_local_ip(): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.connect(('8.8.8.8', 80)) # Google DNS 服务器 + ip_address = sock.getsockname()[0] + sock.close() + return ip_address + + +current_file_path = Path(__file__).resolve() +base_dir = current_file_path.parent +config_path = base_dir / "config/config.yaml" + + +class ConfigMap(dict): + __setattr__ = dict.__setitem__ + __getattr__ = dict.__getitem__ + + +with open(str(config_path), mode='r', encoding='utf-8') as fd: + data = yaml.load(fd, Loader=yaml.FullLoader) + _config = data.get(data.get("CurrentConfig", "DevelopmentConfig")) +config = ConfigMap() +for k, v in _config.items(): + config[k] = v +config['base_dir'] = base_dir +database = _config.get("database") +if database: + if database.get("type") == "sqlite": + database_uri = f'sqlite:///{base_dir}/{database.get("path")}' + elif database.get("type") == "mysql": + database_uri = f'mysql+pymysql://{database.get("user")}:{database.get("password")}@{database.get("host")}:{database.get("port")}/{database.get("database")}?' + else: + database_uri = '' + config['SQLALCHEMY_DATABASE_URI'] = database_uri + +ip_address = get_local_ip() +port = config.get("PORT", 5559) +# 配置 SERVER_NAME +config['SERVER_NAME'] = f'{ip_address}:5559' +# 配置 APPLICATION_ROOT +config['APPLICATION_ROOT'] = '/' +# 配置 PREFERRED_URL_SCHEME +config['PREFERRED_URL_SCHEME'] = 'http' + +app = create_app(config) + +if __name__ == '__main__': + app.run(host="0.0.0.0", port=port, debug=config.get("DEBUG", False)) diff --git a/projects/web_api/web_api/common/__init__.py b/projects/web_api/web_api/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/web_api/web_api/common/custom_response.py b/projects/web_api/web_api/common/custom_response.py new file mode 100644 index 00000000..7d244c1a --- /dev/null +++ b/projects/web_api/web_api/common/custom_response.py @@ -0,0 +1,23 @@ +from flask import jsonify + + +class ResponseCode: + SUCCESS = 200 + PARAM_WARING = 400 + MESSAGE = "success" + + +def generate_response(data=None, code=ResponseCode.SUCCESS, msg=ResponseCode.MESSAGE, **kwargs): + """ + 自定义响应 + :param code:状态码 + :param data:返回数据 + :param msg:返回消息 + :param kwargs: + :return: + """ + msg = msg or 'success' if code == 200 else msg or 'fail' + success = True if code == 200 else False + res = jsonify(dict(code=code, success=success, data=data, msg=msg, **kwargs)) + res.status_code = 200 + return res diff --git a/projects/web_api/web_api/common/error_types.py b/projects/web_api/web_api/common/error_types.py new file mode 100644 index 00000000..8624437d --- /dev/null +++ b/projects/web_api/web_api/common/error_types.py @@ -0,0 +1,45 @@ +import json +from flask import request +from werkzeug.exceptions import HTTPException + + +class ApiException(HTTPException): + """API错误基类""" + code = 500 + msg = 'Sorry, we made a mistake Σ(っ °Д °;)っ' + msgZH = "" + error_code = 999 + + def __init__(self, msg=None, msgZH=None, code=None, error_code=None, headers=None): + if code: + self.code = code + if msg: + self.msg = msg + if msgZH: + self.msgZH = msgZH + if error_code: + self.error_code = error_code + super(ApiException, self).__init__(msg, None) + + @staticmethod + def get_error_url(): + """获取出错路由和请求方式""" + method = request.method + full_path = str(request.full_path) + main_path = full_path.split('?')[0] + res = method + ' ' + main_path + return res + + def get_body(self, environ=None, scope=None): + """异常返回信息""" + body = dict( + msg=self.msg, + error_code=self.error_code, + request=self.get_error_url() + ) + text = json.dumps(body) + return text + + def get_headers(self, environ=None, scope=None): + """异常返回格式""" + return [("Content-Type", "application/json")] \ No newline at end of file diff --git a/projects/web_api/web_api/common/ext.py b/projects/web_api/web_api/common/ext.py new file mode 100644 index 00000000..783fe3c5 --- /dev/null +++ b/projects/web_api/web_api/common/ext.py @@ -0,0 +1,80 @@ +import hashlib +import mimetypes + + +def is_pdf(filename, file): + """ + 判断文件是否为PDF格式。 + + :param filename: 文件名 + :param file: 文件对象 + :return: 如果文件是PDF格式,则返回True,否则返回False + """ + # 检查文件扩展名 https://arxiv.org/pdf/2405.08702 pdf链接可能存在不带扩展名的情况,先注释 + if not filename.endswith('.pdf'): + return False + + # 检查MIME类型 + mime_type, _ = mimetypes.guess_type(filename) + print(mime_type) + if mime_type != 'application/pdf': + return False + + # 可选:读取文件的前几KB内容并检查MIME类型 + # 这一步是可选的,用于更严格的检查 + # if not mimetypes.guess_type(filename, strict=False)[0] == 'application/pdf': + # return False + + # 检查文件内容 + file_start = file.read(5) + file.seek(0) + if not file_start.startswith(b'%PDF-'): + return False + + return True + + +def url_is_pdf(file): + """ + 判断文件是否为PDF格式。 + + :param file: 文件对象 + :return: 如果文件是PDF格式,则返回True,否则返回False + """ + # 检查文件内容 + file_start = file.read(5) + file.seek(0) + if not file_start.startswith(b'%PDF-'): + return False + + return True + + +def calculate_file_hash(file, algorithm='sha256'): + """ + 计算给定文件的哈希值。 + + :param file: 文件对象 + :param algorithm: 哈希算法的名字,如:'sha256', 'md5', 'sha1'等 + :return: 文件的哈希值 + """ + hash_func = getattr(hashlib, algorithm)() + block_size = 65536 # 64KB chunks + # with open(file_path, 'rb') as file: + buffer = file.read(block_size) + while len(buffer) > 0: + hash_func.update(buffer) + buffer = file.read(block_size) + file.seek(0) + return hash_func.hexdigest() + + +def singleton_func(cls): + instance = {} + + def _singleton(*args, **kwargs): + if cls not in instance: + instance[cls] = cls(*args, **kwargs) + return instance[cls] + + return _singleton diff --git a/projects/web_api/web_api/common/import_models.py b/projects/web_api/web_api/common/import_models.py new file mode 100644 index 00000000..06af38e3 --- /dev/null +++ b/projects/web_api/web_api/common/import_models.py @@ -0,0 +1 @@ +from api.analysis.models import * \ No newline at end of file diff --git a/projects/web_api/web_api/common/logger.py b/projects/web_api/web_api/common/logger.py new file mode 100644 index 00000000..b994afc9 --- /dev/null +++ b/projects/web_api/web_api/common/logger.py @@ -0,0 +1,19 @@ +import os +from loguru import logger +from pathlib import Path +from datetime import datetime + + +def setup_log(config): + """ + Setup logging + :param config: config file + :return: + """ + log_path = os.path.join(Path(__file__).parent.parent, "log") + if not Path(log_path).exists(): + Path(log_path).mkdir(parents=True, exist_ok=True) + log_level = config.get("LOG_LEVEL") + log_name = f'log_{datetime.now().strftime("%Y-%m-%d")}.log' + log_file_path = os.path.join(log_path, log_name) + logger.add(str(log_file_path), rotation='00:00', encoding='utf-8', level=log_level, enqueue=True) diff --git a/projects/web_api/web_api/common/web_hook.py b/projects/web_api/web_api/common/web_hook.py new file mode 100644 index 00000000..41982a0a --- /dev/null +++ b/projects/web_api/web_api/common/web_hook.py @@ -0,0 +1,9 @@ + +def before_request(): + return None + + +def after_request(response): + response.headers.add('Access-Control-Allow-Origin', '*') + response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization') + return response diff --git a/projects/web_api/web_api/config/__init__.py b/projects/web_api/web_api/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/projects/web_api/web_api/config/config.yaml b/projects/web_api/web_api/config/config.yaml new file mode 100644 index 00000000..1bdb5cf3 --- /dev/null +++ b/projects/web_api/web_api/config/config.yaml @@ -0,0 +1,31 @@ +# 基本配置 +BaseConfig: &base + DEBUG: false + PORT: 5559 + LOG_LEVEL: "DEBUG" + SQLALCHEMY_TRACK_MODIFICATIONS: true + SQLALCHEMY_DATABASE_URI: "" + PROPAGATE_EXCEPTIONS: true + SECRET_KEY: "#$%^&**$##*(*^%%$**((&" + JWT_SECRET_KEY: "#$%^&**$##*(*^%%$**((&" + JWT_ACCESS_TOKEN_EXPIRES: 3600 + PDF_UPLOAD_FOLDER: "upload_pdf" + PDF_ANALYSIS_FOLDER: "analysis_pdf" + +# 开发配置 +DevelopmentConfig: + <<: *base + database: + type: sqlite + path: config/mineru_web.db + +# 生产配置 +ProductionConfig: + <<: *base + +# 测试配置 +TestingConfig: + <<: *base + +# 当前使用配置 +CurrentConfig: "DevelopmentConfig" diff --git a/projects/web_api/web_api/config/mineru_web.db b/projects/web_api/web_api/config/mineru_web.db new file mode 100644 index 0000000000000000000000000000000000000000..299d9af2b68d2df1ff4e62281255a1a385068b97 GIT binary patch literal 557056 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�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

U*06QKa44N1d?F_5$ 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


Hy%K+bBbEa~J?`uhcr=Z%&Qd7Ogp$t4$oAEM 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 S%-QT=-}s#(0Zzt8)BhjFblLI% literal 0 HcmV?d00001 diff --git a/projects/web_api/web_api/static/__init__.py b/projects/web_api/web_api/static/__init__.py new file mode 100644 index 00000000..e69de29b