diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8c9bb533..1f04d9ba8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,15 +106,6 @@ jobs: matrix: python-version: ['3.8', '3.9', '3.10'] fail-fast: false - env: - # Enable Tika end to end testing - TIKA_LIVE: 1 - # Enable paperless_mail testing against real server - PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }} - PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }} - PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }} - # Enable Gotenberg end to end testing - GOTENBERG_LIVE: 1 steps: - name: Checkout @@ -156,12 +147,18 @@ jobs: pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list - name: Tests + env: + PAPERLESS_CI_TEST: 1 + # Enable paperless_mail testing against real server + PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }} + PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }} + PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }} run: | cd src/ pipenv --python ${{ steps.setup-python.outputs.python-version }} run pytest -ra - name: Upload coverage to Codecov - if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION && github.event_name == 'push'}} + if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }} uses: codecov/codecov-action@v3 with: # not required for public repos, but intermittently fails otherwise @@ -309,7 +306,7 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.docker-meta.outputs.tags }} labels: ${{ steps.docker-meta.outputs.labels }} - # Get cache layers from this branch, then dev, then main + # Get cache layers from this branch, then dev # This allows new branches to get at least some cache benefits, generally from dev cache-from: | type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }} diff --git a/.github/workflows/cleanup-tags.yml b/.github/workflows/cleanup-tags.yml index a54716b53..bbb7a169c 100644 --- a/.github/workflows/cleanup-tags.yml +++ b/.github/workflows/cleanup-tags.yml @@ -51,14 +51,6 @@ jobs: include: - primary-name: "paperless-ngx" - primary-name: "paperless-ngx/builder/cache/app" - - primary-name: "paperless-ngx/builder/qpdf" - - primary-name: "paperless-ngx/builder/cache/qpdf" - - primary-name: "paperless-ngx/builder/pikepdf" - - primary-name: "paperless-ngx/builder/cache/pikepdf" - - primary-name: "paperless-ngx/builder/jbig2enc" - - primary-name: "paperless-ngx/builder/cache/jbig2enc" - - primary-name: "paperless-ngx/builder/psycopg2" - - primary-name: "paperless-ngx/builder/cache/psycopg2" # TODO: Remove the above and replace with the below # - primary-name: "builder/qpdf" # - primary-name: "builder/cache/qpdf" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c0aeec47..98178a6bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: exclude: "(^Pipfile\\.lock$)" # Python hooks - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.265' + rev: 'v0.0.272' hooks: - id: ruff - repo: https://github.com/psf/black @@ -57,6 +57,6 @@ repos: args: - "--tab" - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.9.0.2" + rev: "v0.9.0.5" hooks: - id: shellcheck diff --git a/Dockerfile b/Dockerfile index ebe95facd..f96feb804 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ # Purpose: Compiles the frontend # Notes: # - Does NPM stuff with Typescript and such -FROM --platform=$BUILDPLATFORM node:16-bullseye-slim AS compile-frontend +FROM --platform=$BUILDPLATFORM docker.io/node:16-bookworm-slim AS compile-frontend COPY ./src-ui /src/src-ui @@ -21,7 +21,7 @@ RUN set -eux \ # Comments: # - pipenv dependencies are not left in the final image # - pipenv can't touch the final image somehow -FROM --platform=$BUILDPLATFORM python:3.9-alpine as pipenv-base +FROM --platform=$BUILDPLATFORM docker.io/python:3.9-alpine as pipenv-base WORKDIR /usr/src/pipenv @@ -37,7 +37,7 @@ RUN set -eux \ # Purpose: The final image # Comments: # - Don't leave anything extra in here -FROM python:3.9-slim-bullseye as main-app +FROM docker.io/python:3.9-slim-bookworm as main-app LABEL org.opencontainers.image.authors="paperless-ngx team " LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/" @@ -70,9 +70,9 @@ ARG RUNTIME_PACKAGES="\ # Image processing liblept5 \ liblcms2-2 \ - libtiff5 \ + libtiff6 \ libfreetype6 \ - libwebp6 \ + libwebp7 \ libopenjp2-7 \ libimagequant0 \ libraqm0 \ @@ -98,6 +98,8 @@ ARG RUNTIME_PACKAGES="\ libxml2 \ libxslt1.1 \ libgnutls30 \ + libqpdf29 \ + qpdf \ # Mime type detection file \ libmagic1 \ @@ -181,7 +183,7 @@ ARG PSYCOPG2_VERSION=2.9.6 RUN set -eux \ && echo "Getting binaries" \ && mkdir paperless-ngx \ - && curl --fail --silent --show-error --output paperless-ngx.tar.gz --location https://github.com/paperless-ngx/builder/archive/3d6574e2dbaa8b8cdced864a256b0de59015f605.tar.gz \ + && curl --fail --silent --show-error --output paperless-ngx.tar.gz --location https://github.com/paperless-ngx/builder/archive/1f0e6665ba1b144f70fd6dfc8d0e8ba3b7a578ee.tar.gz \ && tar -xf paperless-ngx.tar.gz --directory paperless-ngx --strip-components=1 \ && cd paperless-ngx \ # Setting a specific revision ensures we know what this installed @@ -189,9 +191,7 @@ RUN set -eux \ && echo "Installing jbig2enc" \ && cp ./jbig2enc/${JBIG2ENC_VERSION}/${TARGETARCH}${TARGETVARIANT}/jbig2 /usr/local/bin/ \ && cp ./jbig2enc/${JBIG2ENC_VERSION}/${TARGETARCH}${TARGETVARIANT}/libjbig2enc* /usr/local/lib/ \ - && echo "Installing qpdf" \ - && apt-get install --yes --no-install-recommends ./qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/libqpdf29_*.deb \ - && apt-get install --yes --no-install-recommends ./qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/qpdf_*.deb \ + && chmod a+x /usr/local/bin/jbig2 \ && echo "Installing pikepdf and dependencies" \ && python3 -m pip install --no-cache-dir ./pikepdf/${PIKEPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/*.whl \ && python3 -m pip list \ @@ -214,8 +214,7 @@ COPY --from=pipenv-base /usr/src/pipenv/requirements.txt ./ ARG BUILD_PACKAGES="\ build-essential \ git \ - default-libmysqlclient-dev \ - python3-dev" + default-libmysqlclient-dev" RUN set -eux \ && echo "Installing build system packages" \ diff --git a/Pipfile b/Pipfile index 49cdc32c2..edb0e46a9 100644 --- a/Pipfile +++ b/Pipfile @@ -37,14 +37,13 @@ psycopg2 = "*" rapidfuzz = "*" redis = {extras = ["hiredis"], version = "*"} scikit-learn = "~=1.2" -numpy = "*" whitenoise = "~=6.3" watchdog = "~=2.2" whoosh="~=2.7" inotifyrecursive = "~=0.3" ocrmypdf = "~=14.0" tqdm = "*" -tika = "*" +tika-client = "*" channels = "~=4.0" channels-redis = "*" uvicorn = {extras = ["standard"], version = "*"} @@ -78,6 +77,7 @@ factory-boy = "*" pytest = "*" pytest-cov = "*" pytest-django = "*" +pytest-httpx = "*" pytest-env = "*" pytest-sugar = "*" pytest-xdist = "*" diff --git a/Pipfile.lock b/Pipfile.lock index c826846be..d948729ef 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "271fd0b623bee180093e65238a9e1fc7bfaf9c292364d479f935da751958bfd4" + "sha256": "db3fc8c37931534327f89c6211581495328b6f6bf2c533df848fa23faa5d0cd3" }, "pipfile-spec": 6, "requires": {}, @@ -75,10 +75,11 @@ }, "billiard": { "hashes": [ - "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547", - "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b" + "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a", + "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5" ], - "version": "==3.6.4.0" + "markers": "python_version >= '3.7'", + "version": "==4.1.0" }, "bleach": { "hashes": [ @@ -180,11 +181,11 @@ "redis" ], "hashes": [ - "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14", - "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d" + "sha256:1eaba5ee14d8c8c0bed8f6063e5e10dabdbcf23503a861cf0e10b7221d99cb0d", + "sha256:95d29f9a93f41c4b122fddf1fe3ef13f872029dca4ad1f9af4f1a414442ceecf" ], "index": "pypi", - "version": "==5.2.7" + "version": "==5.3.0" }, "certifi": { "hashes": [ @@ -480,11 +481,11 @@ }, "django-extensions": { "hashes": [ - "sha256:2a4f4d757be2563cd1ff7cfdf2e57468f5f931cc88b23cf82ca75717aae504a4", - "sha256:421464be390289513f86cb5e18eb43e5dc1de8b4c27ba9faa3b91261b0d67e09" + "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", + "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401" ], "index": "pypi", - "version": "==3.2.1" + "version": "==3.2.3" }, "django-filter": { "hashes": [ @@ -528,11 +529,11 @@ }, "filelock": { "hashes": [ - "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9", - "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718" + "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81", + "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec" ], "index": "pypi", - "version": "==3.12.0" + "version": "==3.12.2" }, "flower": { "hashes": [ @@ -652,6 +653,14 @@ ], "version": "==2.2.3" }, + "httpcore": { + "hashes": [ + "sha256:125f8375ab60036db632f34f4b627a9ad085048eef7cb7d2616fea0f739f98af", + "sha256:5581b9c12379c4288fe70f43c710d16060c10080617001e6b22a3b6dbcbefd36" + ], + "markers": "python_version >= '3.7'", + "version": "==0.17.2" + }, "httptools": { "hashes": [ "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9", @@ -698,6 +707,14 @@ ], "version": "==0.5.0" }, + "httpx": { + "hashes": [ + "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", + "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd" + ], + "markers": "python_version >= '3.7'", + "version": "==0.24.1" + }, "humanfriendly": { "hashes": [ "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", @@ -769,11 +786,11 @@ }, "kombu": { "hashes": [ - "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610", - "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4" + "sha256:d084ec1f96f7a7c37ba9e816823bdbc08f0fc7ddb3a5be555805e692102297d8", + "sha256:fa9be55281bb351ba9da582b2a74e3dd5015b8d075b287e4d16f0b2f25fefcc2" ], - "markers": "python_version >= '3.7'", - "version": "==5.2.4" + "markers": "python_version >= '3.8'", + "version": "==5.3.0" }, "langdetect": { "hashes": [ @@ -986,7 +1003,7 @@ "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c", "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b" ], - "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==1.24.3" }, "ocrmypdf": { @@ -1301,101 +1318,101 @@ }, "rapidfuzz": { "hashes": [ - "sha256:05130d9d33c4770116037de9f131e488825165105588cc7143f77733c5b25a6f", - "sha256:0b81d15da16e97c288c645eb642d8a08d0ab98b827efb2682cab282a45893efe", - "sha256:0bf1953a4c32ce6e2f3ed1897d0a8dbbbf19456ef0a8e37bae26e007d9fb5096", - "sha256:0f7041c550b69d35675e04dc3f0690d0c26499039e942a0b1604c6547951e6fc", - "sha256:14e9924108a26f2f58aa8cb90f1a106398fa43e359fa5a96b0f328c7bb7f76da", - "sha256:1a5eb2d1844f375f34e3c83b1a03094293d894472fdd1a095cf35e4dfa2ecf01", - "sha256:1a8c372dc278d1d5ced7cdc99ad8cc1d3de0b245e769a6b327c462b98873e5d4", - "sha256:1fe6ea9300f347fd3352c755aa04d71a2786afa008d1af1a35830e6a44e7fd5f", - "sha256:2132d7724c03dd322035cf1b0c23ca9e7e449ec2b7225040a2ca2fa3f1a3bbfa", - "sha256:24d77723bb20030a91b096326d14b673d2ff1f0c7bbc64ed519992ed8eb5b869", - "sha256:284f216f0500cd977830f107da5c3f96e91356dc7993512efc414dbd55679d51", - "sha256:2b50f2d429d81a65910f5ee9b14e172e300a09b8b2ecb91d3e4efc5d2583915c", - "sha256:2bbf9aad86b70283362dc2db3cacb7dcde0ffe6027f54feb0ccb23cf87b6aa11", - "sha256:339b94c536ab9c1b1bac245fb6814df3ba104603d2c1a97f8fb41922357bd772", - "sha256:351df02889ac3da9f3f7b10e6812740927cfab9def453079da94f83697b03f2f", - "sha256:35506d04429440333224c3711dfdb4195d34eff733cb48648d0c89a9b99faf14", - "sha256:35e21f0718fd1c1853f8f433f2f84f618f6d4a6d9d96bb7c42e39797be600b58", - "sha256:3a5670d5475777450fbc70ed8de8d4e3f7c69230a8b539f45bda358a6f9699f2", - "sha256:3ab635a544243e1924508bfc3f294c28bdced6d74388ac25041d3dabcaefab75", - "sha256:3ba129c3c8202e8bef0d9964b8798913905ad1dc6293e94d7a02d87cdbef2544", - "sha256:3c40a626050e37c2f74e2ba00538578d3c4a6baa171d08ed5091b6a03512ac4a", - "sha256:3f51d35521f86e767d3e640d0ab42908d01c3e05cf54ac1f0b547f3f602800f1", - "sha256:4032713943c32fff97d863e6618162923e3b3c31917d437126d9fcf7e33c83d2", - "sha256:41e98b3cebfa3e720186eeab37e6c0565895edf848fd958c34ab94c39e743311", - "sha256:481c0389c3b26cd2aa498b6924ca6e9a1d1dd5b15ad5f009d273292949e47e24", - "sha256:494b613a3e730e08df1c7c14e45c303a0f5c8a701162bfc8ac9079585837de43", - "sha256:4a94fe0a42da816e4a6279ac4c23e4ba6de86a529b61b08d5e8e2633b29c781b", - "sha256:4c1d895d16f62e9ac88d303eb918d90a390bd712055c849e01c558b7ae0fa908", - "sha256:4e1da3dce34d742567da0722b9c8dc2b51554ab5a22fdaf763b60209445a7b37", - "sha256:4ef3a6aa07b996c789c8d5ab99ed0563d551d32fa9330fd0f52ba28d20fcb662", - "sha256:526df35d07083480e751f9679fd1f3e8a0819e8a13586e3860db5b65549a408a", - "sha256:54fb70b667b882f9939bc6f581957fcb47fec2e7ad652259835c80e9e30230c9", - "sha256:562caa39652c72156574fcf60ce7adf29964a031a57ae977c180947e00425b4a", - "sha256:5759bb0fd13ee030626e8bbb5b644092a043817fb192335ff4c481402b1edd0e", - "sha256:59c6c9d3ca7d84c5878a74d142816350a3bdfb51e4d10ac104afd396168481f6", - "sha256:5d8664d6f844ea9018b4866e8a8dbf49c87f703668b1b3265de83aa3c9941272", - "sha256:5f241ca0bcbfcbf90bb48bb1c8dbc1fddc205bee5520f898b994adda3d3f150a", - "sha256:5fdcc1ce830bf46fbc098c8b6eb3201a8299476153bae7a5d5e86576f7228d0a", - "sha256:62aac81ef17bab9f664828b9087d0afe5a94ed48396b0456a2503b68e3d567f2", - "sha256:62c760748b1253e08ab5138855e8f8d2c25a7cb5a0bfad74bb66db63c27d8a50", - "sha256:658be4cabcc229f52a902f5e87205e1b9c29c66e463a267c8d8f237acde56002", - "sha256:69270e0cd850984f562b2239d07cde2213e5c3642cd8d550d5ac9a0fcd0882df", - "sha256:6bf090b9b4ec4df5f0899bbc4055b8b173b33169186d4de1dd3d9c609bd330a2", - "sha256:6dbaa605c0f81208efbf166afb23f73b0f3847a1a966bec828f4167f61d0ca4b", - "sha256:6dfd138dcc0920b71c1d1bc017413b032286a1f33488613dce9e254c454abaf2", - "sha256:6ee35eddeddb5f5750d2a9cc55894926969fa0bac80bbe57211ae6fd0d34b39f", - "sha256:6ff8835a3ba17f3baf3838f2612e3758d7b1ca09eb16c9a382df3bec5bb9bda3", - "sha256:738ae2d59ab254c4f173b40b00a9c1f092697949284c59e0879e6e3beb337a69", - "sha256:76da6c8972acd58c31efdd09c4c85263ba3b4394d2c2929be4c171a22293bab3", - "sha256:7c81891e570e50d0afe43f722f426b0bd602d3c5315f0f290514511d9520b1e6", - "sha256:7eea0ca53da78040a6b7bb041af8051c52efa7facc6f18dce33e679f2decaf62", - "sha256:7f8d89b16b4752deeb66dd321548c4cfa59819982d43d2ae7ab5d6e0f15bee94", - "sha256:82a4ea0742b9e375d4856714ef59241007765edbce34fd2f7d76c552ed93a7d2", - "sha256:832d953b5f1462eba5a0830ea7df11b784f090ba5409fc92bccb856d2539b618", - "sha256:87eb7e9fb49265c33bda0417cc74c474a891cae60735fbbd75d79a106483888e", - "sha256:8883267df996b42494f40d533ef3a3fea247531d137773a649fb851747ae12c8", - "sha256:88c9e93508128168708aae3ef98eeb422a88204d81ac4492fcea1e1162a6af74", - "sha256:8b5d78052e840191b9c7556eb3bd4fe52435e58bd979c75298b65262368dd1fa", - "sha256:938cc766d0ce9eca95549593b6ca7ff86a2917b9e68c1989ad95485aed0f49dd", - "sha256:9a9b7d22e46ada4e6a1f1404c267f3f023b44594929913d855f14bc5fb11b53d", - "sha256:9c263840bda0f532714ecd66f1f82ed3d3460f45e79e8a907f4df8eaafd93d31", - "sha256:a0c22a36b611a2d53fada2cb03b276135d08c2703039078ce985d7cc42734fd7", - "sha256:a0e0d8798b7048c9db4e139bafb21792013fb043df07bfaf0d3dc9e1df2be5e6", - "sha256:a149c944f3c349f6279a8cc4cbfa3e80cc2baaec9d983359698aa792faa44653", - "sha256:abc2f05c1f30b9533cb9b85d73c28d93aa99c7ae2992df04c1704fcaf248b59c", - "sha256:aeb855b62bc351884a672b8f87875c552492d9199c78f33cc8650c283fd7504d", - "sha256:b85dfb6f0c353c4b37499529f9831620a7bdc61c375e07f8c38b595f93e906e5", - "sha256:bc593306faa6c73e50cb31b81efbb580957272b14c5cf6bcf0233adf8a7e118d", - "sha256:bf5ddf48f63895f949355a1c4d643e0a531c9317d52901f80d5a6299d967b766", - "sha256:c50825de43442c4625a2ca1d948c911d116cf9007ad7f29cd27561c99b16947c", - "sha256:c94fe53da481d8580e6410f3e7e4ba4e9c5786cad1f289fbb6c9c9585c6d78e1", - "sha256:c9ca5f4ae767605cefa5156f5fa8561bee61849f9b2ccfb165d7087b4f9af90c", - "sha256:cf8b1b29028dc1bc6a5654f22425ee6d3967bbd44bc3a117be0f43b03300f928", - "sha256:d128f615da9a198cd9b33be658a0c26fabe06a6d28fa4652953853e8d174c2c6", - "sha256:d24054843f4cfbb86df608ec1209e6a29b0d2635230577a94e38a9cfa3880d18", - "sha256:d25555297466ab5ded3875913fc0bfa78b89b0a32d79bd65ffbd32ae71f07c2d", - "sha256:db57085c9dbd0b1005d6ad905c610920c49d0752f522d2f34477b13cba24e1d1", - "sha256:dc1a39d1cc8e679c7240b2d1ed8366cf740ab8429cc9b582ebd94a5c6ededbe5", - "sha256:dc39af05cdf89be426d96fce579c812948a324b022fb11dfea1e99e180d4f68b", - "sha256:dd2ad4160d8ad9a2abdad1c765fd29e4d9b6b8ce6a707ee48eb2869e7dff0f89", - "sha256:e058ecfd8edb04b221d1b2d005f17be932075a16f75b100b275de1d3d220da5f", - "sha256:e334225a97824d9f75f5cf8e949e129bc183f0762f4c9b7a127d1809461bdc55", - "sha256:e85b4f4aeb994c841478e98a9e05bcb7ed8ead084d93bd2ca0683dc5e93b1c36", - "sha256:ea5e25378591a698ae5076c582a0135db2cb43270fb2866737ab4cb6fcc34474", - "sha256:ebf96d236a52058c010f354256e8de4274621a7f8b5a15dffa54d9b6a1c7e6e8", - "sha256:f00a8b3d0b21884ea972d5430797b1a25b9d2c715b3eaf03366903aac5d8398c", - "sha256:f213bb5d4cd0b1fddf209bafe2d2896320a737fbded3a567d454e54875e4d9cc", - "sha256:f5767b0e8220b6f8afcc1fe77529e5678470f9e94a1cfc9e29f5b0721dc1496c", - "sha256:f80986d3c8d55b848d679084231a35273320f658e64f0d86d725bb360e6cd2c4", - "sha256:fa098429af4e17fb5bacb0c39f1f8349891356ba7ca540521515b5708fec4a76", - "sha256:fbe4b8305cb427b49d70c182a01c91fd85112e0573193a1f9e4fbcec35ea3eff", - "sha256:fc764665ba19b923696eae6912a2f0fc52bdd7db6c53be178d1dd70eb72f2f68" + "sha256:0843c53d54d5b7d6122d8f1d7574d8c91a7aacc5c316f74d6e33d98aec82949d", + "sha256:086a2d84c2e497e3ab160ccf164e319bca874d9383d008fcadf91ede8ac7997f", + "sha256:09a6f5cd9f1282da49b8d0747c40f3fea2d64ab5e4c2cc2295baf87ff7a0d062", + "sha256:0de229cb613be060580c71c1674acbde57921c7ed33d7a726e071a2562924113", + "sha256:10313075642a9f1f948d356f4f0803ae28a496d7967b466b9cae1a4be8aa4df3", + "sha256:10f56af1d46fbeaaa0dc50901c2dc439c7a455cfdac2f1acf6cffeb65ae82c48", + "sha256:1465ea085154378e69bf4bc5e27bdac5c94684416882ace31865232adc9239a2", + "sha256:15260263a0c7bffac934a53b6622d77e06e10929ee4d2e62ac6f70c13988f351", + "sha256:167dbce2da6bb5b73d43e53434c5a9d7d1214b658b315420e44044782f4c482b", + "sha256:16c506bac2e0a6f6581b334a7802c2f0d8343ec1d77e5cf9452c33d6219abef8", + "sha256:17b017f9e1b88dfd6d9b03170ef8e86477de0d9d37fbfcbe72ca070cacbe1b65", + "sha256:17e4cbe6632aae7c35101c4b7c498e83f6eacf61be0def4ff98167df30dc69ca", + "sha256:25eea5c8006b6c8747ca204675c9e939f3c4d27167fb43b2aa211443d34f9abd", + "sha256:32a5c47b5153f25eb512dbb91f9850225d2dcfb3404a1c48406726c7732b0726", + "sha256:351d253fdee62d6d0e80c75f0505accc1ce8cc73a50779c60986ef21c92f20f9", + "sha256:362e366e79fcc9a8866b41f20ef4d2987a06f8b134096e659594c059aa8a6d88", + "sha256:39c7d0dbd77a7f28ff85a1dff2afb2ed73e5cd81cca3f654450ed339a271c0ab", + "sha256:3b3e953dcef0302eeb4fe8c7c4907e50d175199fc07da05ad6bd1d8d141ff138", + "sha256:3f2cd9a3760080876fc59edb26926e51d6db44dea65e85f1eb04aa5f58c3bc41", + "sha256:4019def8a18bc867ac61f08a542bf474a7a9b3f662f5d5cd169c9135866562f5", + "sha256:408007b4bc5a0a0cb9bfcdcc8cffa9b71fec6ee53ccdf9c26b57539f7e264ab5", + "sha256:49d900da023eeb3bfbe9feee126312eb9fd0458129aa5a581e4d8d8bf4483d14", + "sha256:4a64ddfb7084b678da7778c1263aee2baae5a2ca55ec5589a022defc38103eb1", + "sha256:4e951c874a0e5b375b2af9b5f264eefc679c0685c166ee0641e703ef0795509b", + "sha256:51bb8f7aa4fe45618e75cdccf08491c752a7f137ffbf7d3afd1809791ac8c326", + "sha256:51f21f37aec6bc117e9083181ddc3cbbcbf56b6506492b128d8e836d3545ca80", + "sha256:53e3c588e7ea158fa80095dd0ff53f49e2ede9a8d71a3a5b964ca045d845a9b9", + "sha256:58ca539cc6ce385d650138a9b1908b05622c2dd08a23d5aea4890523ef3774d5", + "sha256:5a371846f45ed9d24927a8d5222884536c1e171543396b36250fafb2e848bc92", + "sha256:5d4d509e9aa011e1be5e4da7c5062dc4fc3688714687110536925980b3d03ac6", + "sha256:5e11e11880951e767342b56627ab2dc9d3ef90e2605b656e9b5e6e0beadaaf0f", + "sha256:68d910048b36613701ea671de68f701e2c1ba2839295238def840ff1fc1b15f4", + "sha256:69d503a7641b5a63aa53c7aca0b857d38f48cd7bae39f8563679b324e3d2d47a", + "sha256:6c0e96821029c46847df4ff266ea283a2b6163a4f76a4567f9986934e9c4410c", + "sha256:6d4da453fbd8793ebb11bed396f8a4b9041d6227bf055903447305dd7942312f", + "sha256:6ede2d42ad55bd4e7a3394e98c5f58ddace78775493391732d32be61268a4116", + "sha256:6f32791ee045a7b3d6a56208a55d996d5f7a32fdb688f5c5ee899cb7589539eb", + "sha256:6f767d4823002e65c06ea273f952fda2b88775e1c2d508564f04d32cdd7f65b2", + "sha256:712331c1c70c79a219c2ac233b4e25e75ffad51042840d147d5e94519c7d8a1a", + "sha256:7726f67e4a0b2b4392f03aa62e16b12a697156c6735df27b21bd3ab561b01659", + "sha256:788fb03c5acb5b48f5f918f4cbb5dc072498becf018c64e7e27d6b76e63e68b8", + "sha256:79f5a3ab7ff6c46336f38690f0564bc7689cefa180257ed9078c42f75b10c9d2", + "sha256:7af18372f576e36e93f4662bdf64043ac23dfa02d7f768d7e7e1d0211bb9cb35", + "sha256:7c74fde444bcd13ef3a803c578b28f33b4f9edf368f46ca3de57fda456065967", + "sha256:7e181411958d04d5b437a0981e87815e8f1b1909f5ae0e339246d3bc464f53e7", + "sha256:7f2024f83a9300440e845b441e71726471f7567021c1d80796ca02e71c5f0dc2", + "sha256:819d9317c3d86b508d87ab1bca5867f3abc18b902c822bc57366ccc6330a030b", + "sha256:8243bb4bb4db7c3501932ced6a978b284e19c3619b6802455e47bfd0905adb81", + "sha256:83b48b789f2da1688882cba595c40179194ab15ec17ea1d4c9de9ee239649904", + "sha256:851b44130393139cb336aa54c681d595d75a3160b7be330f3acc0c3b9dabce70", + "sha256:88e77ed7d0bd8d9be530c462c921904ada8d3417671eed749784c5a315af334d", + "sha256:8b966344ed4122a71ab8ccdca2954db1ce0d8049cb9bcac58db07558f9d9ec32", + "sha256:8c07e16ab38e717931319cff1340debbf2ef940a1cda4eb70e323079b62df306", + "sha256:8c85bb6946fb02231d1e60ab45c36ecee04ecf7f725e094f5beee798b6b7d36d", + "sha256:91946c496e6f380939dbea14ff6ce6de87480445c09d03964f5374101462594b", + "sha256:9dc7154889937ca5a004d17f62b4798e0af52f69c38eb3112dbdb52b006d4419", + "sha256:9ff1a517de2b1e80ddf1a3037a6ebca9925154c1af70751518d50d5c332e1ec8", + "sha256:a06a08be3cb7d7df7993dd16e84aaf59bd5a7ff98a9f1b3e893d18b273a71c64", + "sha256:a293370448f2e46fdc6e086ac99923015bdc53973a65d3df35aefc685e1a5809", + "sha256:a4a751f216fd1222a4a8c7ceff5180872a156202c3bdca1b337e5a5b09298dfd", + "sha256:a4afab735bb0ac3ec9bafcc35376ed336d26af6140c4d81e4c869e77df77ecd5", + "sha256:a8b8f32463781e4703965c9cf7a609a19a74478f332e0d62cd9d0e7a9db91321", + "sha256:a8bb256b34fcad4f3fa00be6b57fe35bcb54f031911195929145c67d9738ffec", + "sha256:aadc5a8b9859737a8f87831215b7fab0c04afeb960bb987c528421a4e6dfb8b6", + "sha256:b1bf8aba99b267aad0a01dfb44ee39803676007724abcfb72129c350476b2341", + "sha256:b408ac3c7f8c3414bfd5c6044ca4bb385b390bcf5eae3ad884cef48628c131ae", + "sha256:b4995792e106c3f1ab6f56dd6089918b065888e2e55a71e3fea8d0f66bf30989", + "sha256:b7c65112c87568274d399ad7a62902cef17801c2bd047b162e79e43758b3ce27", + "sha256:c089ce856919e03f4dd8f9168d60ac580d30cd0451fd60dcdef73010eca68973", + "sha256:c53cf36cdb10819b7154fefdbffbef442ba567d9c1ca74a7e76fd759ace45e6c", + "sha256:cb08db5c122fea4196483b82f7596e50ef9cab1770f7696c197bf0815ac4dd17", + "sha256:ccc1b5b467766110085c80bb9311d233fccc8ed1ce965aebba3125e1bab04cba", + "sha256:cdbf9a76ea47f14026daaed43a2c2150ab0e9a4d5396909f028380f33e61c522", + "sha256:cdee4f4d04761ce167538adbefa01a64e7cab949d89aa09df39ef0d5e859fb2a", + "sha256:d3264e4a02e4148e30078104fb0c1b6c8eb166ddc5ebe843a22433f58f87dc47", + "sha256:d5fe8054c244bf63be2380efc275edd86da3a706460d42911dc3ff914f3260a5", + "sha256:d72916d27fb88741bfb576b0b0639354ca00f5e91046171c985262c68a86bbb5", + "sha256:db5e71e5a810d2f1163c914e01b3ba241409a98286ac4850ff26076115ae401b", + "sha256:dc7f25e20781c8d42e813516ee4ff9043ecce4a8e25fc94ee6732a83d81c1c99", + "sha256:de784bbe06d32e66617cd20766c37aae2438902d54b3fa608d2e0a929ca705f4", + "sha256:e0755f5ac6c3d1dc2505eb2e6eaf5508ff17b42c084406714fbabf2d50d098b6", + "sha256:e549da8d68ad4ee385c918ea8b9efeda875df9edf6c6b48df927bd061c00bfef", + "sha256:e6772eb7cc4429f1eae5a9b41e5b0b1af8f0d50727c6e338d9ad5bceee01da5a", + "sha256:ea3e46a534de97a6cad2018cb950492a0fcacad380e35440ce3c1c8fef96a261", + "sha256:ec5523d5c08c639cd4e301d42f3ad7c6fb061a1f1cd6b5b627e59af345edfed7", + "sha256:ef3ad80458e47723812976a2ea1282ff207ad20e6cb19da1917f76699bd5aaa5", + "sha256:ef6c38040d868dcc0132fad377aafeb5b2da71354759e77f41ae599316df2dee", + "sha256:f1e23665be5918f979180130babedab9317fbb34cdae237c7defad7e86bc684e", + "sha256:f25d1975e846d07990cf946a5927a932aa7cccd308ae9979b03a58ff1cd80087", + "sha256:f7acc5c9c7cf567372de5b6c817f93db508e7b9bd7f29bd6187df8d2cc60ced5", + "sha256:fb7049dff52cded65184a3d2ff45cfd226bff7314f49a8f4b83f943eea9181a7", + "sha256:fdd2ab5ab56fcaf839a9f58caa8756dbfeba0b3dc187850b763d0a1e6ee9c97a" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.1.1" }, "redis": { "extras": [ @@ -1410,97 +1427,97 @@ }, "regex": { "hashes": [ - "sha256:02f4541550459c08fdd6f97aa4e24c6f1932eec780d58a2faa2068253df7d6ff", - "sha256:0a69cf0c00c4d4a929c6c7717fd918414cab0d6132a49a6d8fc3ded1988ed2ea", - "sha256:0bbd5dcb19603ab8d2781fac60114fb89aee8494f4505ae7ad141a3314abb1f9", - "sha256:10250a093741ec7bf74bcd2039e697f519b028518f605ff2aa7ac1e9c9f97423", - "sha256:10374c84ee58c44575b667310d5bbfa89fb2e64e52349720a0182c0017512f6c", - "sha256:1189fbbb21e2c117fda5303653b61905aeeeea23de4a94d400b0487eb16d2d60", - "sha256:1307aa4daa1cbb23823d8238e1f61292fd07e4e5d8d38a6efff00b67a7cdb764", - "sha256:144b5b017646b5a9392a5554a1e5db0000ae637be4971c9747566775fc96e1b2", - "sha256:171c52e320fe29260da550d81c6b99f6f8402450dc7777ef5ced2e848f3b6f8f", - "sha256:18196c16a584619c7c1d843497c069955d7629ad4a3fdee240eb347f4a2c9dbe", - "sha256:18f05d14f14a812fe9723f13afafefe6b74ca042d99f8884e62dbd34dcccf3e2", - "sha256:1ecf3dcff71f0c0fe3e555201cbe749fa66aae8d18f80d2cc4de8e66df37390a", - "sha256:21e90a288e6ba4bf44c25c6a946cb9b0f00b73044d74308b5e0afd190338297c", - "sha256:23d86ad2121b3c4fc78c58f95e19173790e22ac05996df69b84e12da5816cb17", - "sha256:256f7f4c6ba145f62f7a441a003c94b8b1af78cee2cccacfc1e835f93bc09426", - "sha256:290fd35219486dfbc00b0de72f455ecdd63e59b528991a6aec9fdfc0ce85672e", - "sha256:2e9c4f778514a560a9c9aa8e5538bee759b55f6c1dcd35613ad72523fd9175b8", - "sha256:338994d3d4ca4cf12f09822e025731a5bdd3a37aaa571fa52659e85ca793fb67", - "sha256:33d430a23b661629661f1fe8395be2004006bc792bb9fc7c53911d661b69dd7e", - "sha256:385992d5ecf1a93cb85adff2f73e0402dd9ac29b71b7006d342cc920816e6f32", - "sha256:3d45864693351c15531f7e76f545ec35000d50848daa833cead96edae1665559", - "sha256:40005cbd383438aecf715a7b47fe1e3dcbc889a36461ed416bdec07e0ef1db66", - "sha256:4035d6945cb961c90c3e1c1ca2feb526175bcfed44dfb1cc77db4fdced060d3e", - "sha256:445d6f4fc3bd9fc2bf0416164454f90acab8858cd5a041403d7a11e3356980e8", - "sha256:48c9ec56579d4ba1c88f42302194b8ae2350265cb60c64b7b9a88dcb7fbde309", - "sha256:4a5059bd585e9e9504ef9c07e4bc15b0a621ba20504388875d66b8b30a5c4d18", - "sha256:4a6e4b0e0531223f53bad07ddf733af490ba2b8367f62342b92b39b29f72735a", - "sha256:4b870b6f632fc74941cadc2a0f3064ed8409e6f8ee226cdfd2a85ae50473aa94", - "sha256:50fd2d9b36938d4dcecbd684777dd12a407add4f9f934f235c66372e630772b0", - "sha256:53e22e4460f0245b468ee645156a4f84d0fc35a12d9ba79bd7d79bdcd2f9629d", - "sha256:586a011f77f8a2da4b888774174cd266e69e917a67ba072c7fc0e91878178a80", - "sha256:59597cd6315d3439ed4b074febe84a439c33928dd34396941b4d377692eca810", - "sha256:59e4b729eae1a0919f9e4c0fc635fbcc9db59c74ad98d684f4877be3d2607dd6", - "sha256:5a0f874ee8c0bc820e649c900243c6d1e6dc435b81da1492046716f14f1a2a96", - "sha256:5ac2b7d341dc1bd102be849d6dd33b09701223a851105b2754339e390be0627a", - "sha256:5e3f4468b8c6fd2fd33c218bbd0a1559e6a6fcf185af8bb0cc43f3b5bfb7d636", - "sha256:6164d4e2a82f9ebd7752a06bd6c504791bedc6418c0196cd0a23afb7f3e12b2d", - "sha256:6893544e06bae009916a5658ce7207e26ed17385149f35a3125f5259951f1bbe", - "sha256:690a17db524ee6ac4a27efc5406530dd90e7a7a69d8360235323d0e5dafb8f5b", - "sha256:6b8d0c153f07a953636b9cdb3011b733cadd4178123ef728ccc4d5969e67f3c2", - "sha256:72a28979cc667e5f82ef433db009184e7ac277844eea0f7f4d254b789517941d", - "sha256:72aa4746993a28c841e05889f3f1b1e5d14df8d3daa157d6001a34c98102b393", - "sha256:732176f5427e72fa2325b05c58ad0b45af341c459910d766f814b0584ac1f9ac", - "sha256:7918a1b83dd70dc04ab5ed24c78ae833ae8ea228cef84e08597c408286edc926", - "sha256:7923470d6056a9590247ff729c05e8e0f06bbd4efa6569c916943cb2d9b68b91", - "sha256:7d76a8a1fc9da08296462a18f16620ba73bcbf5909e42383b253ef34d9d5141e", - "sha256:811040d7f3dd9c55eb0d8b00b5dcb7fd9ae1761c454f444fd9f37fe5ec57143a", - "sha256:821a88b878b6589c5068f4cc2cfeb2c64e343a196bc9d7ac68ea8c2a776acd46", - "sha256:84397d3f750d153ebd7f958efaa92b45fea170200e2df5e0e1fd4d85b7e3f58a", - "sha256:844671c9c1150fcdac46d43198364034b961bd520f2c4fdaabfc7c7d7138a2dd", - "sha256:890a09cb0a62198bff92eda98b2b507305dd3abf974778bae3287f98b48907d3", - "sha256:8f08276466fedb9e36e5193a96cb944928301152879ec20c2d723d1031cd4ddd", - "sha256:8f5e06df94fff8c4c85f98c6487f6636848e1dc85ce17ab7d1931df4a081f657", - "sha256:921473a93bcea4d00295799ab929522fc650e85c6b9f27ae1e6bb32a790ea7d3", - "sha256:941b3f1b2392f0bcd6abf1bc7a322787d6db4e7457be6d1ffd3a693426a755f2", - "sha256:9b320677521aabf666cdd6e99baee4fb5ac3996349c3b7f8e7c4eee1c00dfe3a", - "sha256:9c3efee9bb53cbe7b285760c81f28ac80dc15fa48b5fe7e58b52752e642553f1", - "sha256:9fda3e50abad8d0f48df621cf75adc73c63f7243cbe0e3b2171392b445401550", - "sha256:a4c5da39bca4f7979eefcbb36efea04471cd68db2d38fcbb4ee2c6d440699833", - "sha256:a56c18f21ac98209da9c54ae3ebb3b6f6e772038681d6cb43b8d53da3b09ee81", - "sha256:a623564d810e7a953ff1357f7799c14bc9beeab699aacc8b7ab7822da1e952b8", - "sha256:a8906669b03c63266b6a7693d1f487b02647beb12adea20f8840c1a087e2dfb5", - "sha256:a99757ad7fe5c8a2bb44829fc57ced11253e10f462233c1255fe03888e06bc19", - "sha256:aa7d032c1d84726aa9edeb6accf079b4caa87151ca9fabacef31fa028186c66d", - "sha256:aad5524c2aedaf9aa14ef1bc9327f8abd915699dea457d339bebbe2f0d218f86", - "sha256:afb1c70ec1e594a547f38ad6bf5e3d60304ce7539e677c1429eebab115bce56e", - "sha256:b6365703e8cf1644b82104cdd05270d1a9f043119a168d66c55684b1b557d008", - "sha256:b8b942d8b3ce765dbc3b1dad0a944712a89b5de290ce8f72681e22b3c55f3cc8", - "sha256:ba73a14e9c8f9ac409863543cde3290dba39098fc261f717dc337ea72d3ebad2", - "sha256:bd7b68fd2e79d59d86dcbc1ccd6e2ca09c505343445daaa4e07f43c8a9cc34da", - "sha256:bd966475e963122ee0a7118ec9024388c602d12ac72860f6eea119a3928be053", - "sha256:c2ce65bdeaf0a386bb3b533a28de3994e8e13b464ac15e1e67e4603dd88787fa", - "sha256:c64d5abe91a3dfe5ff250c6bb267ef00dbc01501518225b45a5f9def458f31fb", - "sha256:c8c143a65ce3ca42e54d8e6fcaf465b6b672ed1c6c90022794a802fb93105d22", - "sha256:cd46f30e758629c3ee91713529cfbe107ac50d27110fdcc326a42ce2acf4dafc", - "sha256:ced02e3bd55e16e89c08bbc8128cff0884d96e7f7a5633d3dc366b6d95fcd1d6", - "sha256:cf123225945aa58b3057d0fba67e8061c62d14cc8a4202630f8057df70189051", - "sha256:d19e57f888b00cd04fc38f5e18d0efbd91ccba2d45039453ab2236e6eec48d4d", - "sha256:d1cbe6b5be3b9b698d8cc4ee4dee7e017ad655e83361cd0ea8e653d65e469468", - "sha256:db09e6c18977a33fea26fe67b7a842f706c67cf8bda1450974d0ae0dd63570df", - "sha256:de2f780c3242ea114dd01f84848655356af4dd561501896c751d7b885ea6d3a1", - "sha256:e2205a81f815b5bb17e46e74cc946c575b484e5f0acfcb805fb252d67e22938d", - "sha256:e645c757183ee0e13f0bbe56508598e2d9cd42b8abc6c0599d53b0d0b8dd1479", - "sha256:f2910502f718828cecc8beff004917dcf577fc5f8f5dd40ffb1ea7612124547b", - "sha256:f764e4dfafa288e2eba21231f455d209f4709436baeebb05bdecfb5d8ddc3d35", - "sha256:f83fe9e10f9d0b6cf580564d4d23845b9d692e4c91bd8be57733958e4c602956", - "sha256:fb2b495dd94b02de8215625948132cc2ea360ae84fe6634cd19b6567709c8ae2", - "sha256:fee0016cc35a8a91e8cc9312ab26a6fe638d484131a7afa79e1ce6165328a135" + "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938", + "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6", + "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef", + "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525", + "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af", + "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc", + "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54", + "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8", + "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7", + "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568", + "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c", + "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f", + "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536", + "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2", + "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb", + "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df", + "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019", + "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18", + "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f", + "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac", + "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8", + "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9", + "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697", + "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06", + "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d", + "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036", + "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1", + "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787", + "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9", + "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0", + "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7", + "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461", + "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee", + "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3", + "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0", + "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f", + "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14", + "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477", + "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd", + "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16", + "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc", + "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff", + "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd", + "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2", + "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e", + "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef", + "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07", + "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3", + "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3", + "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a", + "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7", + "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa", + "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82", + "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77", + "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222", + "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c", + "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd", + "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d", + "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68", + "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9", + "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693", + "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487", + "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06", + "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591", + "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27", + "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a", + "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969", + "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3", + "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0", + "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7", + "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751", + "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747", + "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289", + "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72", + "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777", + "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9", + "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1", + "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1", + "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e", + "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf", + "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f", + "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd", + "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2", + "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c", + "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1", + "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88", + "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9", + "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.5" + "version": "==2023.6.3" }, "reportlab": { "hashes": [ @@ -1553,14 +1570,6 @@ "index": "pypi", "version": "==3.6.12" }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, "scikit-learn": { "hashes": [ "sha256:065e9673e24e0dc5113e2dd2b4ca30c9d8aa2fa90f4c0597241c93b63130d233", @@ -1735,13 +1744,13 @@ "markers": "python_version >= '3.6'", "version": "==3.1.0" }, - "tika": { + "tika-client": { "hashes": [ - "sha256:3b136ae517db6c69c5ddee3a6a5c98e8966fedfc7c9155ebaaf3b9269121f992", - "sha256:56670eb812944eb25ed73f1b3b075aa41e7a135b74b240822f28b819e5b373da" + "sha256:43b53816b3783c9c77e16df314cad5ad66ab606391c26ad4bc94a784d473a156", + "sha256:e1ef3447b4307059e4a836e3786088498637323733f83a2f807b77f998d77610" ], "index": "pypi", - "version": "==2.6.0" + "version": "==0.0.3" }, "tornado": { "hashes": [ @@ -1770,11 +1779,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c", - "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98" + "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26", + "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5" ], "markers": "python_version < '3.10'", - "version": "==4.6.2" + "version": "==4.6.3" + }, + "tzdata": { + "hashes": [ + "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", + "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" + ], + "markers": "python_version >= '2'", + "version": "==2023.3" }, "tzlocal": { "hashes": [ @@ -1784,14 +1801,6 @@ "markers": "python_version >= '3.7'", "version": "==5.0.1" }, - "urllib3": { - "hashes": [ - "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", - "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.2" - }, "uvicorn": { "extras": [ "standard" @@ -2095,6 +2104,14 @@ } }, "develop": { + "anyio": { + "hashes": [ + "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", + "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0" + ], + "markers": "python_version >= '3.7'", + "version": "==3.7.0" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -2350,60 +2367,69 @@ "toml" ], "hashes": [ - "sha256:004948e296149644d208964300cb3d98affc5211e9e490e9979af4030b0d6473", - "sha256:13cde6bb0e58fb67d09e2f373de3899d1d1e866c5a9ff05d93615f2f54fbd2bb", - "sha256:1c9e4a5eb1bbc3675ee57bc31f8eea4cd7fb0cbcbe4912cf1cb2bf3b754f4a80", - "sha256:2025f913f2edb0272ef15d00b1f335ff8908c921c8eb2013536fcaf61f5a683d", - "sha256:25bad4196104761bc26b1dae9b57383826542ec689ff0042f7f4f4dd7a815cba", - "sha256:2692306d3d4cb32d2cceed1e47cebd6b1d2565c993d6d2eda8e6e6adf53301e6", - "sha256:272ab31228a9df857ab5df5d67936d8861464dc89c5d3fab35132626e9369379", - "sha256:2e8c0e79820cdd67978e1120983786422d279e07a381dbf89d03bbb23ec670a6", - "sha256:3062fd5c62df988cea9f2972c593f77fed1182bfddc5a3b12b1e606cb7aba99e", - "sha256:3436927d1794fa6763b89b60c896f9e3bd53212001026ebc9080d23f0c2733c1", - "sha256:35db06450272473eab4449e9c2ad9bc6a0a68dab8e81a0eae6b50d9c2838767e", - "sha256:392154d09bd4473b9d11351ab5d63391f3d5d24d752f27b3be7498b0ee2b5226", - "sha256:3cff6980fe7100242170092bb40d2b1cdad79502cd532fd26b12a2b8a5f9aee0", - "sha256:42c692b55a647a832025a4c048007034fe77b162b566ad537ce65ad824b12a84", - "sha256:44c9b9f1a245f3d0d202b1a8fa666a80b5ecbe4ad5d0859c0fb16a52d9763224", - "sha256:496b86f1fc9c81a1cd53d8842ef712e950a4611bba0c42d33366a7b91ba969ec", - "sha256:4bbd58eb5a2371bf160590f4262109f66b6043b0b991930693134cb617bc0169", - "sha256:4e3783a286d5a93a2921396d50ce45a909aa8f13eee964465012f110f0cbb611", - "sha256:4f3c7c19581d471af0e9cb49d928172cd8492cd78a2b7a4e82345d33662929bb", - "sha256:52c139b7ab3f0b15f9aad0a3fedef5a1f8c0b2bdc291d88639ca2c97d3682416", - "sha256:541280dde49ce74a4262c5e395b48ea1207e78454788887118c421cb4ffbfcac", - "sha256:5906f6a84b47f995cd1bf0aca1c72d591c55ee955f98074e93660d64dfc66eb9", - "sha256:6284a2005e4f8061c58c814b1600ad0074ccb0289fe61ea709655c5969877b70", - "sha256:6727a0d929ff0028b1ed8b3e7f8701670b1d7032f219110b55476bb60c390bfb", - "sha256:697f4742aa3f26c107ddcb2b1784a74fe40180014edbd9adaa574eac0529914c", - "sha256:6b9f64526286255735847aed0221b189486e0b9ed943446936e41b7e44b08783", - "sha256:6babcbf1e66e46052442f10833cfc4a0d3554d8276aa37af8531a83ed3c1a01d", - "sha256:6e7f1a8328eeec34c54f1d5968a708b50fc38d31e62ca8b0560e84a968fbf9a9", - "sha256:71f739f97f5f80627f1fee2331e63261355fd1e9a9cce0016394b6707ac3f4ec", - "sha256:76d06b721c2550c01a60e5d3093f417168658fb454e5dfd9a23570e9bffe39a1", - "sha256:77a04b84d01f0e12c66f16e69e92616442dc675bbe51b90bfb074b1e5d1c7fbd", - "sha256:97729e6828643f168a2a3f07848e1b1b94a366b13a9f5aba5484c2215724edc8", - "sha256:9a8723ccec4e564d4b9a79923246f7b9a8de4ec55fa03ec4ec804459dade3c4f", - "sha256:a5ffd45c6b93c23a8507e2f436983015c6457aa832496b6a095505ca2f63e8f1", - "sha256:a6f03f87fea579d55e0b690d28f5042ec1368650466520fbc400e7aeaf09e995", - "sha256:aac1d5fdc5378f6bac2c0c7ebe7635a6809f5b4376f6cf5d43243c1917a67087", - "sha256:ae82c5f168d2a39a5d69a12a69d4dc23837a43cf2ca99be60dfe59996ea6b113", - "sha256:bc7b667f8654376e9353dd93e55e12ce2a59fb6d8e29fce40de682273425e044", - "sha256:c1d7a31603c3483ac49c1726723b0934f88f2c011c660e6471e7bd735c2fa110", - "sha256:c534431153caffc7c495c3eddf7e6a6033e7f81d78385b4e41611b51e8870446", - "sha256:c93d52c3dc7b9c65e39473704988602300e3cc1bad08b5ab5b03ca98bbbc68c1", - "sha256:cbcc874f454ee51f158afd604a315f30c0e31dff1d5d5bf499fc529229d964dd", - "sha256:d3cacc6a665221108ecdf90517a8028d07a2783df3417d12dcfef1c517e67478", - "sha256:d712cefff15c712329113b01088ba71bbcef0f7ea58478ca0bbec63a824844cb", - "sha256:d7786b2fa7809bf835f830779ad285215a04da76293164bb6745796873f0942d", - "sha256:dc11b42fa61ff1e788dd095726a0aed6aad9c03d5c5984b54cb9e1e67b276aa5", - "sha256:dc4d5187ef4d53e0d4c8eaf530233685667844c5fb0b855fea71ae659017854b", - "sha256:f5440cdaf3099e7ab17a5a7065aed59aff8c8b079597b61c1f8be6f32fe60636", - "sha256:fa079995432037b5e2ef5ddbb270bcd2ded9f52b8e191a5de11fe59a00ea30d8", - "sha256:fbe6e8c0a9a7193ba10ee52977d4d5e7652957c1f56ccefed0701db8801a2a3b", - "sha256:fde5c7a9d9864d3e07992f66767a9817f24324f354caa3d8129735a3dc74f126" + "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", + "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", + "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a", + "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", + "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", + "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6", + "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", + "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f", + "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02", + "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c", + "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", + "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", + "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5", + "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959", + "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", + "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", + "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", + "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", + "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5", + "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f", + "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", + "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", + "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9", + "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f", + "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", + "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", + "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", + "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", + "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", + "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e", + "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", + "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5", + "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", + "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", + "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", + "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", + "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", + "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", + "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9", + "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", + "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0", + "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", + "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", + "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050", + "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", + "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", + "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", + "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb", + "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e", + "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", + "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", + "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", + "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd", + "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", + "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", + "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", + "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", + "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", + "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", + "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3" ], "markers": "python_version >= '3.7'", - "version": "==7.2.6" + "version": "==7.2.7" }, "cryptography": { "hashes": [ @@ -2471,19 +2497,19 @@ }, "faker": { "hashes": [ - "sha256:80a5ea1464556c06b98bf47ea3adc7f33811a1182518d847860b1874080bd3c9", - "sha256:defe9ed618a67ebf0f3eb1895e198c2355a7128a09087a6dce342ef2253263ea" + "sha256:633b278caa3ec239463f9139c74da2607c8da5710e56d5d7d30fc8a7440104c4", + "sha256:d9f363720c4a6cf9884c6c3e26e2ce26266ffe5d741a9bc7cb9256779bc62190" ], "markers": "python_version >= '3.7'", - "version": "==18.9.0" + "version": "==18.10.1" }, "filelock": { "hashes": [ - "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9", - "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718" + "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81", + "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec" ], "index": "pypi", - "version": "==3.12.0" + "version": "==3.12.2" }, "ghp-import": { "hashes": [ @@ -2492,6 +2518,30 @@ ], "version": "==2.1.0" }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:125f8375ab60036db632f34f4b627a9ad085048eef7cb7d2616fea0f739f98af", + "sha256:5581b9c12379c4288fe70f43c710d16060c10080617001e6b22a3b6dbcbefd36" + ], + "markers": "python_version >= '3.7'", + "version": "==0.17.2" + }, + "httpx": { + "hashes": [ + "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd", + "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd" + ], + "markers": "python_version >= '3.7'", + "version": "==0.24.1" + }, "hyperlink": { "hashes": [ "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", @@ -2564,59 +2614,59 @@ }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "version": "==2.1.3" }, "mergedeep": { "hashes": [ @@ -2636,11 +2686,11 @@ }, "mkdocs-material": { "hashes": [ - "sha256:1ae74cc5464ef2f64574d4884512efed7f4db386fb9bc6af20fd427d7a702f49", - "sha256:b56a9f955ed32d38333715cbbf68ce38f683bf38610c65094fa4ef2db9f08bcd" + "sha256:8513ab847c9a541ed3d11a3a7eed556caf72991ee786c31c5aac6691a121088a", + "sha256:b49e12869ab464558e2dd3c5792da5b748a7e0c48ee83b4d05715f98125a7a39" ], "index": "pypi", - "version": "==9.1.14" + "version": "==9.1.15" }, "mkdocs-material-extensions": { "hashes": [ @@ -2697,7 +2747,7 @@ "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c", "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b" ], - "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==1.24.3" }, "packaging": { @@ -2798,11 +2848,11 @@ }, "platformdirs": { "hashes": [ - "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f", - "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5" + "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed", + "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e" ], "markers": "python_version >= '3.7'", - "version": "==3.5.1" + "version": "==3.5.3" }, "pluggy": { "hashes": [ @@ -2868,11 +2918,11 @@ }, "pytest": { "hashes": [ - "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362", - "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3" + "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295", + "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b" ], "index": "pypi", - "version": "==7.3.1" + "version": "==7.3.2" }, "pytest-cov": { "hashes": [ @@ -2898,6 +2948,14 @@ "index": "pypi", "version": "==0.8.1" }, + "pytest-httpx": { + "hashes": [ + "sha256:3a82797f3a9a14d51e8c6b7fa97524b68b847ee801109c062e696b4744f4431c", + "sha256:cefb7dcf66a4cb0601b0de05e576cca423b6081f3245e7912a4d84c58fa3eae8" + ], + "index": "pypi", + "version": "==0.22.0" + }, "pytest-sugar": { "hashes": [ "sha256:8cb5a4e5f8bbcd834622b0235db9e50432f4cbd71fef55b467fe44e43701e062", @@ -3008,97 +3066,97 @@ }, "regex": { "hashes": [ - "sha256:02f4541550459c08fdd6f97aa4e24c6f1932eec780d58a2faa2068253df7d6ff", - "sha256:0a69cf0c00c4d4a929c6c7717fd918414cab0d6132a49a6d8fc3ded1988ed2ea", - "sha256:0bbd5dcb19603ab8d2781fac60114fb89aee8494f4505ae7ad141a3314abb1f9", - "sha256:10250a093741ec7bf74bcd2039e697f519b028518f605ff2aa7ac1e9c9f97423", - "sha256:10374c84ee58c44575b667310d5bbfa89fb2e64e52349720a0182c0017512f6c", - "sha256:1189fbbb21e2c117fda5303653b61905aeeeea23de4a94d400b0487eb16d2d60", - "sha256:1307aa4daa1cbb23823d8238e1f61292fd07e4e5d8d38a6efff00b67a7cdb764", - "sha256:144b5b017646b5a9392a5554a1e5db0000ae637be4971c9747566775fc96e1b2", - "sha256:171c52e320fe29260da550d81c6b99f6f8402450dc7777ef5ced2e848f3b6f8f", - "sha256:18196c16a584619c7c1d843497c069955d7629ad4a3fdee240eb347f4a2c9dbe", - "sha256:18f05d14f14a812fe9723f13afafefe6b74ca042d99f8884e62dbd34dcccf3e2", - "sha256:1ecf3dcff71f0c0fe3e555201cbe749fa66aae8d18f80d2cc4de8e66df37390a", - "sha256:21e90a288e6ba4bf44c25c6a946cb9b0f00b73044d74308b5e0afd190338297c", - "sha256:23d86ad2121b3c4fc78c58f95e19173790e22ac05996df69b84e12da5816cb17", - "sha256:256f7f4c6ba145f62f7a441a003c94b8b1af78cee2cccacfc1e835f93bc09426", - "sha256:290fd35219486dfbc00b0de72f455ecdd63e59b528991a6aec9fdfc0ce85672e", - "sha256:2e9c4f778514a560a9c9aa8e5538bee759b55f6c1dcd35613ad72523fd9175b8", - "sha256:338994d3d4ca4cf12f09822e025731a5bdd3a37aaa571fa52659e85ca793fb67", - "sha256:33d430a23b661629661f1fe8395be2004006bc792bb9fc7c53911d661b69dd7e", - "sha256:385992d5ecf1a93cb85adff2f73e0402dd9ac29b71b7006d342cc920816e6f32", - "sha256:3d45864693351c15531f7e76f545ec35000d50848daa833cead96edae1665559", - "sha256:40005cbd383438aecf715a7b47fe1e3dcbc889a36461ed416bdec07e0ef1db66", - "sha256:4035d6945cb961c90c3e1c1ca2feb526175bcfed44dfb1cc77db4fdced060d3e", - "sha256:445d6f4fc3bd9fc2bf0416164454f90acab8858cd5a041403d7a11e3356980e8", - "sha256:48c9ec56579d4ba1c88f42302194b8ae2350265cb60c64b7b9a88dcb7fbde309", - "sha256:4a5059bd585e9e9504ef9c07e4bc15b0a621ba20504388875d66b8b30a5c4d18", - "sha256:4a6e4b0e0531223f53bad07ddf733af490ba2b8367f62342b92b39b29f72735a", - "sha256:4b870b6f632fc74941cadc2a0f3064ed8409e6f8ee226cdfd2a85ae50473aa94", - "sha256:50fd2d9b36938d4dcecbd684777dd12a407add4f9f934f235c66372e630772b0", - "sha256:53e22e4460f0245b468ee645156a4f84d0fc35a12d9ba79bd7d79bdcd2f9629d", - "sha256:586a011f77f8a2da4b888774174cd266e69e917a67ba072c7fc0e91878178a80", - "sha256:59597cd6315d3439ed4b074febe84a439c33928dd34396941b4d377692eca810", - "sha256:59e4b729eae1a0919f9e4c0fc635fbcc9db59c74ad98d684f4877be3d2607dd6", - "sha256:5a0f874ee8c0bc820e649c900243c6d1e6dc435b81da1492046716f14f1a2a96", - "sha256:5ac2b7d341dc1bd102be849d6dd33b09701223a851105b2754339e390be0627a", - "sha256:5e3f4468b8c6fd2fd33c218bbd0a1559e6a6fcf185af8bb0cc43f3b5bfb7d636", - "sha256:6164d4e2a82f9ebd7752a06bd6c504791bedc6418c0196cd0a23afb7f3e12b2d", - "sha256:6893544e06bae009916a5658ce7207e26ed17385149f35a3125f5259951f1bbe", - "sha256:690a17db524ee6ac4a27efc5406530dd90e7a7a69d8360235323d0e5dafb8f5b", - "sha256:6b8d0c153f07a953636b9cdb3011b733cadd4178123ef728ccc4d5969e67f3c2", - "sha256:72a28979cc667e5f82ef433db009184e7ac277844eea0f7f4d254b789517941d", - "sha256:72aa4746993a28c841e05889f3f1b1e5d14df8d3daa157d6001a34c98102b393", - "sha256:732176f5427e72fa2325b05c58ad0b45af341c459910d766f814b0584ac1f9ac", - "sha256:7918a1b83dd70dc04ab5ed24c78ae833ae8ea228cef84e08597c408286edc926", - "sha256:7923470d6056a9590247ff729c05e8e0f06bbd4efa6569c916943cb2d9b68b91", - "sha256:7d76a8a1fc9da08296462a18f16620ba73bcbf5909e42383b253ef34d9d5141e", - "sha256:811040d7f3dd9c55eb0d8b00b5dcb7fd9ae1761c454f444fd9f37fe5ec57143a", - "sha256:821a88b878b6589c5068f4cc2cfeb2c64e343a196bc9d7ac68ea8c2a776acd46", - "sha256:84397d3f750d153ebd7f958efaa92b45fea170200e2df5e0e1fd4d85b7e3f58a", - "sha256:844671c9c1150fcdac46d43198364034b961bd520f2c4fdaabfc7c7d7138a2dd", - "sha256:890a09cb0a62198bff92eda98b2b507305dd3abf974778bae3287f98b48907d3", - "sha256:8f08276466fedb9e36e5193a96cb944928301152879ec20c2d723d1031cd4ddd", - "sha256:8f5e06df94fff8c4c85f98c6487f6636848e1dc85ce17ab7d1931df4a081f657", - "sha256:921473a93bcea4d00295799ab929522fc650e85c6b9f27ae1e6bb32a790ea7d3", - "sha256:941b3f1b2392f0bcd6abf1bc7a322787d6db4e7457be6d1ffd3a693426a755f2", - "sha256:9b320677521aabf666cdd6e99baee4fb5ac3996349c3b7f8e7c4eee1c00dfe3a", - "sha256:9c3efee9bb53cbe7b285760c81f28ac80dc15fa48b5fe7e58b52752e642553f1", - "sha256:9fda3e50abad8d0f48df621cf75adc73c63f7243cbe0e3b2171392b445401550", - "sha256:a4c5da39bca4f7979eefcbb36efea04471cd68db2d38fcbb4ee2c6d440699833", - "sha256:a56c18f21ac98209da9c54ae3ebb3b6f6e772038681d6cb43b8d53da3b09ee81", - "sha256:a623564d810e7a953ff1357f7799c14bc9beeab699aacc8b7ab7822da1e952b8", - "sha256:a8906669b03c63266b6a7693d1f487b02647beb12adea20f8840c1a087e2dfb5", - "sha256:a99757ad7fe5c8a2bb44829fc57ced11253e10f462233c1255fe03888e06bc19", - "sha256:aa7d032c1d84726aa9edeb6accf079b4caa87151ca9fabacef31fa028186c66d", - "sha256:aad5524c2aedaf9aa14ef1bc9327f8abd915699dea457d339bebbe2f0d218f86", - "sha256:afb1c70ec1e594a547f38ad6bf5e3d60304ce7539e677c1429eebab115bce56e", - "sha256:b6365703e8cf1644b82104cdd05270d1a9f043119a168d66c55684b1b557d008", - "sha256:b8b942d8b3ce765dbc3b1dad0a944712a89b5de290ce8f72681e22b3c55f3cc8", - "sha256:ba73a14e9c8f9ac409863543cde3290dba39098fc261f717dc337ea72d3ebad2", - "sha256:bd7b68fd2e79d59d86dcbc1ccd6e2ca09c505343445daaa4e07f43c8a9cc34da", - "sha256:bd966475e963122ee0a7118ec9024388c602d12ac72860f6eea119a3928be053", - "sha256:c2ce65bdeaf0a386bb3b533a28de3994e8e13b464ac15e1e67e4603dd88787fa", - "sha256:c64d5abe91a3dfe5ff250c6bb267ef00dbc01501518225b45a5f9def458f31fb", - "sha256:c8c143a65ce3ca42e54d8e6fcaf465b6b672ed1c6c90022794a802fb93105d22", - "sha256:cd46f30e758629c3ee91713529cfbe107ac50d27110fdcc326a42ce2acf4dafc", - "sha256:ced02e3bd55e16e89c08bbc8128cff0884d96e7f7a5633d3dc366b6d95fcd1d6", - "sha256:cf123225945aa58b3057d0fba67e8061c62d14cc8a4202630f8057df70189051", - "sha256:d19e57f888b00cd04fc38f5e18d0efbd91ccba2d45039453ab2236e6eec48d4d", - "sha256:d1cbe6b5be3b9b698d8cc4ee4dee7e017ad655e83361cd0ea8e653d65e469468", - "sha256:db09e6c18977a33fea26fe67b7a842f706c67cf8bda1450974d0ae0dd63570df", - "sha256:de2f780c3242ea114dd01f84848655356af4dd561501896c751d7b885ea6d3a1", - "sha256:e2205a81f815b5bb17e46e74cc946c575b484e5f0acfcb805fb252d67e22938d", - "sha256:e645c757183ee0e13f0bbe56508598e2d9cd42b8abc6c0599d53b0d0b8dd1479", - "sha256:f2910502f718828cecc8beff004917dcf577fc5f8f5dd40ffb1ea7612124547b", - "sha256:f764e4dfafa288e2eba21231f455d209f4709436baeebb05bdecfb5d8ddc3d35", - "sha256:f83fe9e10f9d0b6cf580564d4d23845b9d692e4c91bd8be57733958e4c602956", - "sha256:fb2b495dd94b02de8215625948132cc2ea360ae84fe6634cd19b6567709c8ae2", - "sha256:fee0016cc35a8a91e8cc9312ab26a6fe638d484131a7afa79e1ce6165328a135" + "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938", + "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6", + "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef", + "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525", + "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af", + "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc", + "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54", + "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8", + "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7", + "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568", + "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c", + "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f", + "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536", + "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2", + "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb", + "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df", + "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019", + "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18", + "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f", + "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac", + "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8", + "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9", + "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697", + "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06", + "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d", + "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036", + "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1", + "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787", + "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9", + "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0", + "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7", + "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461", + "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee", + "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3", + "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0", + "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f", + "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14", + "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477", + "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd", + "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16", + "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc", + "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff", + "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd", + "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2", + "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e", + "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef", + "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07", + "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3", + "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3", + "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a", + "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7", + "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa", + "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82", + "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77", + "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222", + "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c", + "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd", + "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d", + "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68", + "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9", + "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693", + "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487", + "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06", + "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591", + "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27", + "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a", + "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969", + "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3", + "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0", + "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7", + "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751", + "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747", + "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289", + "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72", + "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777", + "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9", + "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1", + "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1", + "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e", + "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf", + "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f", + "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd", + "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2", + "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c", + "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1", + "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88", + "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9", + "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.5" + "version": "==2023.6.3" }, "requests": { "hashes": [ @@ -3110,26 +3168,26 @@ }, "ruff": { "hashes": [ - "sha256:0012f9b7dc137ab7f1f0355e3c4ca49b562baf6c9fa1180948deeb6648c52957", - "sha256:08188f8351f4c0b6216e8463df0a76eb57894ca59a3da65e4ed205db980fd3ae", - "sha256:0827b074635d37984fc98d99316bfab5c8b1231bb83e60dacc83bd92883eedb4", - "sha256:0bbfbf6fd2436165566ca85f6e57be03ed2f0a994faf40180cfbb3604c9232ef", - "sha256:0d61ae4841313f6eeb8292dc349bef27b4ce426e62c36e80ceedc3824e408734", - "sha256:0eb412f20e77529a01fb94d578b19dcb8331b56f93632aa0cce4a2ea27b7aeba", - "sha256:21f00e47ab2308617c44435c8dfd9e2e03897461c9e647ec942deb2a235b4cfd", - "sha256:3ed3b198768d2b3a2300fb18f730cd39948a5cc36ba29ae9d4639a11040880be", - "sha256:643de865fd35cb76c4f0739aea5afe7b8e4d40d623df7e9e6ea99054e5cead0a", - "sha256:739495d2dbde87cf4e3110c8d27bc20febf93112539a968a4e02c26f0deccd1d", - "sha256:8af391ef81f7be960be10886a3c1aac0b298bde7cb9a86ec2b05faeb2081ce6b", - "sha256:95db07b7850b30ebf32b27fe98bc39e0ab99db3985edbbf0754d399eb2f0e690", - "sha256:9613456b0b375766244c25045e353bc8890c856431cd97893c97b10cc93bd28d", - "sha256:b4c037fe2f75bcd9aed0c89c7c507cb7fa59abae2bd4c8b6fc331a28178655a4", - "sha256:b775e2c5fc869359daf8c8b8aa0fd67240201ab2e8d536d14a0edf279af18786", - "sha256:eca02e709b3308eb7255b5f74e779be23b5980fca3862eae28bb23069cd61ae4", - "sha256:f74c4d550f7b8e808455ac77bbce38daafc458434815ba0bc21ae4bdb276509b" + "sha256:06b8ee4eb8711ab119db51028dd9f5384b44728c23586424fd6e241a5b9c4a3b", + "sha256:1609b864a8d7ee75a8c07578bdea0a7db75a144404e75ef3162e0042bfdc100d", + "sha256:19643d448f76b1eb8a764719072e9c885968971bfba872e14e7257e08bc2f2b7", + "sha256:273a01dc8c3c4fd4c2af7ea7a67c8d39bb09bce466e640dd170034da75d14cab", + "sha256:27b2ea68d2aa69fff1b20b67636b1e3e22a6a39e476c880da1282c3e4bf6ee5a", + "sha256:48eccf225615e106341a641f826b15224b8a4240b84269ead62f0afd6d7e2d95", + "sha256:677284430ac539bb23421a2b431b4ebc588097ef3ef918d0e0a8d8ed31fea216", + "sha256:691d72a00a99707a4e0b2846690961157aef7b17b6b884f6b4420a9f25cd39b5", + "sha256:86bc788245361a8148ff98667da938a01e1606b28a45e50ac977b09d3ad2c538", + "sha256:905ff8f3d6206ad56fcd70674453527b9011c8b0dc73ead27618426feff6908e", + "sha256:9c4bfb75456a8e1efe14c52fcefb89cfb8f2a0d31ed8d804b82c6cf2dc29c42c", + "sha256:a37ec80e238ead2969b746d7d1b6b0d31aa799498e9ba4281ab505b93e1f4b28", + "sha256:ae9b57546e118660175d45d264b87e9b4c19405c75b587b6e4d21e6a17bf4fdf", + "sha256:bd2bbe337a3f84958f796c77820d55ac2db1e6753f39d1d1baed44e07f13f96d", + "sha256:d5a208f8ef0e51d4746930589f54f9f92f84bb69a7d15b1de34ce80a7681bc00", + "sha256:dc406e5d756d932da95f3af082814d2467943631a587339ee65e5a4f4fbe83eb", + "sha256:ee76b4f05fcfff37bd6ac209d1370520d509ea70b5a637bdf0a04d0c99e13dff" ], "index": "pypi", - "version": "==0.0.270" + "version": "==0.0.272" }, "scipy": { "hashes": [ @@ -3183,6 +3241,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, "termcolor": { "hashes": [ "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475", @@ -3220,19 +3286,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c", - "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98" + "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26", + "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5" ], "markers": "python_version < '3.10'", - "version": "==4.6.2" + "version": "==4.6.3" }, "urllib3": { "hashes": [ - "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", - "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1", + "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825" ], "markers": "python_version >= '3.7'", - "version": "==2.0.2" + "version": "==2.0.3" }, "virtualenv": { "hashes": [ @@ -3354,11 +3420,11 @@ }, "celery-types": { "hashes": [ - "sha256:1bcdd44614248cca1ac962017cf76392e16a86e2f9a0404e683063fe4e6a10b1", - "sha256:806a5a62aeeebc59f20d96fb075b1c1fe28a31bfc6cb57ae9b4e2e4cceb38f88" + "sha256:324f52a936d36636236c8caca48f4dddb2d5077971d04275ac0959018a9d3d5e", + "sha256:c130770514e68069363ca3b27759bb9d34bd7e99fcfa7ad2469588f9f55478b4" ], "index": "pypi", - "version": "==0.15.0" + "version": "==0.17.0" }, "certifi": { "hashes": [ @@ -3564,30 +3630,30 @@ "compatible-mypy" ], "hashes": [ - "sha256:93baff824f0a056e71036b423b942a74f07b909e45e3fa38185b910f597c5c08", - "sha256:d2c671989efb3f7b0fa91e461909ad5a5a52155fe7fe6d1f2058cb88e3afb123" + "sha256:66477bdba25407623f4079205e58f3c7265a4f0d8f7c9f540a6edc16f8883a5b", + "sha256:8c15d5f7b05926805cfb25f2bfbf3509c37792fbd8aec5aedea358b85d8bccd5" ], "index": "pypi", - "version": "==4.2.0" + "version": "==4.2.1" }, "django-stubs-ext": { "hashes": [ - "sha256:55b2e3077f883e0131a7596f8ff8b19f8fc3ca325a3318ccacf5331acb2601e4", - "sha256:7789f0caeca7152fef07ad6b94dec7310a05d0b8dab77f7979e19db0037b5127" + "sha256:2696d6f7d8538341b060cffa9565c72ea797e866687e040b86d29cad8799e5fe", + "sha256:4b6b63e49f4ba30d93ec46f87507648c99c9de6911e651ad69db7084fd5b2f4e" ], - "markers": "python_version >= '3.7'", - "version": "==4.2.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.1" }, "djangorestframework-stubs": { "extras": [ "compatible-mypy" ], "hashes": [ - "sha256:bbcb8b312581b9543992619cc191b77a5c0ea0c17a2d44752906c3d51a532cca", - "sha256:c15de85f1b34200e157886238505c74b55de0fe0a20c37791b30a3f83c28a0c0" + "sha256:67816705adffc8407fd5469b102abc2db9987afca68dd6f7d21b66369008a940", + "sha256:a1c6619148f08c4070fdf8b92ce2b2b47e10c9610fb25ae27baf2baefb1bf613" ], "index": "pypi", - "version": "==3.14.0" + "version": "==3.14.1" }, "idna": { "hashes": [ @@ -3599,35 +3665,35 @@ }, "mypy": { "hashes": [ - "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521", - "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140", - "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48", - "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128", - "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336", - "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a", - "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41", - "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f", - "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e", - "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8", - "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238", - "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119", - "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb", - "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d", - "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed", - "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9", - "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e", - "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a", - "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5", - "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950", - "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937", - "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394", - "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6", - "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602", - "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1", - "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba" + "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703", + "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf", + "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4", + "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85", + "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd", + "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae", + "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd", + "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca", + "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305", + "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409", + "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c", + "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb", + "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee", + "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a", + "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228", + "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897", + "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d", + "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f", + "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152", + "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf", + "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8", + "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11", + "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017", + "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929", + "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e", + "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" }, "mypy-extensions": { "hashes": [ @@ -3749,10 +3815,10 @@ }, "types-pyopenssl": { "hashes": [ - "sha256:ad024b07a1f4bffbca44699543c71efd04733a6c22781fa9673a971e410a3086", - "sha256:e7211088eff3e20d359888dedecb0994f7181d5cce0f26354dd47ca0484dc8a6" + "sha256:43e307e8dfb3a7a8208a19874ca060305f460c529d4eaca8a2669ea89499f244", + "sha256:ba803a99440b0c2e9ab4e197084aeefc55bdfe8a580d367b2aa4210810a21240" ], - "version": "==23.1.0.3" + "version": "==23.2.0.0" }, "types-python-dateutil": { "hashes": [ @@ -3786,10 +3852,10 @@ }, "types-requests": { "hashes": [ - "sha256:7c5cea7940f8e92ec560bbc468f65bf684aa3dcf0554a6f8c4710f5f708dc598", - "sha256:c1c29d20ab8d84dff468d7febfe8e0cb0b4664543221b386605e14672b44ea25" + "sha256:3de667cffa123ce698591de0ad7db034a5317457a596eb0b4944e5a9d9e8d1ac", + "sha256:afb06ef8f25ba83d59a1d424bd7a5a939082f94b94e90ab5e6116bd2559deaa3" ], - "version": "==2.31.0.0" + "version": "==2.31.0.1" }, "types-setuptools": { "hashes": [ @@ -3816,19 +3882,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c", - "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98" + "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26", + "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5" ], "markers": "python_version < '3.10'", - "version": "==4.6.2" + "version": "==4.6.3" }, "urllib3": { "hashes": [ - "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", - "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1", + "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825" ], "markers": "python_version >= '3.7'", - "version": "==2.0.2" + "version": "==2.0.3" } } } diff --git a/docker/docker-prepare.sh b/docker/docker-prepare.sh index 9cf41d42c..6e5f6889a 100755 --- a/docker/docker-prepare.sh +++ b/docker/docker-prepare.sh @@ -80,7 +80,7 @@ django_checks() { search_index() { - local -r index_version=5 + local -r index_version=6 local -r index_version_file=${DATA_DIR}/.index_version if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then diff --git a/docker/wait-for-redis.py b/docker/wait-for-redis.py index d6ce5c639..41df5a08b 100755 --- a/docker/wait-for-redis.py +++ b/docker/wait-for-redis.py @@ -28,7 +28,7 @@ if __name__ == "__main__": except Exception as e: print( f"Redis ping #{attempt} failed.\n" - f"Error: {str(e)}.\n" + f"Error: {e!s}.\n" f"Waiting {RETRY_SLEEP_SECONDS}s", flush=True, ) diff --git a/docs/configuration.md b/docs/configuration.md index 35dc86ffb..d3874256f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -136,11 +136,11 @@ changed here. Defaults to unset, using the documented path in the home directory. -`PAPERLESS_DB_TIMEOUT=` +`PAPERLESS_DB_TIMEOUT=` : Amount of time for a database connection to wait for the database to -unlock. Mostly applicable for an sqlite based installation, consider -changing to postgresql if you need to increase this. +unlock. Mostly applicable for sqlite based installation. Consider changing +to postgresql if you are having concurrency problems with sqlite. Defaults to unset, keeping the Django defaults. diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 9d05ee9ab..b52c8e702 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -2106,6 +2106,10 @@ src/app/components/common/tag/tag.component.html 8 + + src/app/components/document-list/document-card-small/document-card-small.component.ts + 80 + Add tag diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html index 3f23d99f9..a4024d70d 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html @@ -29,7 +29,7 @@

- {{(document.correspondent$ | async)?.name}}: + {{(document.correspondent$ | async)?.name ?? privateName}}: {{document.title | documentTitle}}

@@ -41,14 +41,14 @@ - {{(document.document_type$ | async)?.name}} + {{(document.document_type$ | async)?.name ?? privateName}}
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index 62f44851e..3dd64818d 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -76,6 +76,10 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions { return this.documentService.getPreviewUrl(this.document.id) } + get privateName() { + return $localize`Private` + } + getTagsLimited$() { const limit = this.document.notes.length > 0 ? 6 : 7 return this.document.tags$.pipe( diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts index 2609af3f3..cc73073f7 100644 --- a/src-ui/src/environments/environment.prod.ts +++ b/src-ui/src/environments/environment.prod.ts @@ -5,7 +5,7 @@ export const environment = { apiBaseUrl: document.baseURI + 'api/', apiVersion: '3', appTitle: 'Paperless-ngx', - version: '1.15.1', + version: '1.15.1-dev', webSocketHost: window.location.host, webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', webSocketBaseUrl: base_url.pathname + 'ws/', diff --git a/src/documents/barcodes.py b/src/documents/barcodes.py index 79fa2746f..f3d59bc5b 100644 --- a/src/documents/barcodes.py +++ b/src/documents/barcodes.py @@ -121,7 +121,7 @@ class BarcodeReader: if barcode.text: barcodes.append(barcode.text) logger.debug( - f"Barcode of type {str(barcode.format)} found: {barcode.text}", + f"Barcode of type {barcode.format} found: {barcode.text}", ) return barcodes @@ -141,7 +141,7 @@ class BarcodeReader: decoded_barcode = barcode.data.decode("utf-8") barcodes.append(decoded_barcode) logger.debug( - f"Barcode of type {str(barcode.type)} found: {decoded_barcode}", + f"Barcode of type {barcode.type} found: {decoded_barcode}", ) return barcodes @@ -180,6 +180,9 @@ class BarcodeReader: with scratch_image.open("rb") as img_file, self.pdf_file.open("wb") as pdf_file: pdf_file.write(img2pdf.convert(img_file)) + # Copy what file stat is possible + shutil.copystat(self.file, self.pdf_file) + def detect(self) -> None: """ Scan all pages of the PDF as images, updating barcodes and the pages @@ -292,6 +295,9 @@ class BarcodeReader: savepath = Path(self.temp_dir.name) / output_filename with open(savepath, "wb") as out: dst.save(out) + + shutil.copystat(self.file, savepath) + document_paths.append(savepath) return document_paths @@ -342,7 +348,7 @@ class BarcodeReader: for idx, document_path in enumerate(doc_paths): if override_name is not None: - newname = f"{str(idx)}_{override_name}" + newname = f"{idx}_{override_name}" dest = save_to_dir / newname else: dest = save_to_dir diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 7ccc8336a..f2da2ff42 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -69,7 +69,7 @@ class Consumer(LoggingMixin): status, message=None, document_id=None, - ): + ): # pragma: no cover payload = { "filename": os.path.basename(self.filename) if self.filename else None, "task_id": self.task_id, @@ -326,7 +326,7 @@ class Consumer(LoggingMixin): dir=settings.SCRATCH_DIR, ) self.path = Path(tempdir.name) / Path(self.filename) - shutil.copy(self.original_path, self.path) + shutil.copy2(self.original_path, self.path) # Determine the parser class. @@ -352,7 +352,7 @@ class Consumer(LoggingMixin): self.run_pre_consume_script() - def progress_callback(current_progress, max_progress): + def progress_callback(current_progress, max_progress): # pragma: no cover # recalculate progress to be within 20 and 80 p = int((current_progress / max_progress) * 50 + 20) self._send_progress(p, 100, "WORKING") @@ -582,6 +582,7 @@ class Consumer(LoggingMixin): def _write(self, storage_type, source, target): with open(source, "rb") as read_file, open(target, "wb") as write_file: write_file.write(read_file.read()) + shutil.copystat(source, target) def _log_script_outputs(self, completed_process: CompletedProcess): """ diff --git a/src/documents/filters.py b/src/documents/filters.py index 53ef0391c..c7f35a6d5 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -116,6 +116,8 @@ class DocumentFilterSet(FilterSet): "created": DATE_KWARGS, "added": DATE_KWARGS, "modified": DATE_KWARGS, + "original_filename": CHAR_KWARGS, + "checksum": CHAR_KWARGS, "correspondent": ["isnull"], "correspondent__id": ID_KWARGS, "correspondent__name": CHAR_KWARGS, diff --git a/src/documents/index.py b/src/documents/index.py index 054a931e0..34e0fd14b 100644 --- a/src/documents/index.py +++ b/src/documents/index.py @@ -64,6 +64,8 @@ def get_schema(): owner_id=NUMERIC(), has_owner=BOOLEAN(), viewer_id=KEYWORD(commas=True), + checksum=TEXT(), + original_filename=TEXT(sortable=True), ) @@ -149,6 +151,8 @@ def update_document(writer: AsyncWriter, doc: Document): owner_id=doc.owner.id if doc.owner else None, has_owner=doc.owner is not None, viewer_id=viewer_ids if viewer_ids else None, + checksum=doc.checksum, + original_filename=doc.original_filename, ) @@ -171,91 +175,85 @@ def remove_document_from_index(document): class DelayedQuery: + param_map = { + "correspondent": ("correspondent", ["id", "id__in", "id__none", "isnull"]), + "document_type": ("type", ["id", "id__in", "id__none", "isnull"]), + "storage_path": ("path", ["id", "id__in", "id__none", "isnull"]), + "owner": ("owner", ["id", "id__in", "id__none", "isnull"]), + "tags": ("tag", ["id__all", "id__in", "id__none"]), + "added": ("added", ["date__lt", "date__gt"]), + "created": ("created", ["date__lt", "date__gt"]), + "checksum": ("checksum", ["icontains", "istartswith"]), + "original_filename": ("original_filename", ["icontains", "istartswith"]), + } + def _get_query(self): raise NotImplementedError def _get_query_filter(self): criterias = [] - for k, v in self.query_params.items(): - if k == "correspondent__id": - criterias.append(query.Term("correspondent_id", v)) - elif k == "correspondent__id__in": - correspondents_in = [] - for correspondent_id in v.split(","): - correspondents_in.append( - query.Term("correspondent_id", correspondent_id), + for key, value in self.query_params.items(): + # is_tagged is a special case + if key == "is_tagged": + criterias.append(query.Term("has_tag", self.evalBoolean(value))) + continue + + # Don't process query params without a filter + if "__" not in key: + continue + + # All other query params consist of a parameter and a query filter + param, query_filter = key.split("__", 1) + try: + field, supported_query_filters = self.param_map[param] + except KeyError: + logger.error(f"Unable to build a query filter for parameter {key}") + continue + + # We only support certain filters per parameter + if query_filter not in supported_query_filters: + logger.info( + f"Query filter {query_filter} not supported for parameter {param}", + ) + continue + + if query_filter == "id": + criterias.append(query.Term(f"{field}_id", value)) + elif query_filter == "id__in": + in_filter = [] + for object_id in value.split(","): + in_filter.append( + query.Term(f"{field}_id", object_id), ) - criterias.append(query.Or(correspondents_in)) - elif k == "correspondent__id__none": - for correspondent_id in v.split(","): + criterias.append(query.Or(in_filter)) + elif query_filter == "id__none": + for object_id in value.split(","): criterias.append( - query.Not(query.Term("correspondent_id", correspondent_id)), + query.Not(query.Term(f"{field}_id", object_id)), ) - elif k == "tags__id__all": - for tag_id in v.split(","): - criterias.append(query.Term("tag_id", tag_id)) - elif k == "tags__id__none": - for tag_id in v.split(","): - criterias.append(query.Not(query.Term("tag_id", tag_id))) - elif k == "tags__id__in": - tags_in = [] - for tag_id in v.split(","): - tags_in.append(query.Term("tag_id", tag_id)) - criterias.append(query.Or(tags_in)) - elif k == "document_type__id": - criterias.append(query.Term("type_id", v)) - elif k == "document_type__id__in": - document_types_in = [] - for document_type_id in v.split(","): - document_types_in.append(query.Term("type_id", document_type_id)) - criterias.append(query.Or(document_types_in)) - elif k == "document_type__id__none": - for document_type_id in v.split(","): - criterias.append(query.Not(query.Term("type_id", document_type_id))) - elif k == "correspondent__isnull": + elif query_filter == "isnull": criterias.append( - query.Term("has_correspondent", self.evalBoolean(v) is False), + query.Term(f"has_{field}", self.evalBoolean(value) is False), ) - elif k == "is_tagged": - criterias.append(query.Term("has_tag", self.evalBoolean(v))) - elif k == "document_type__isnull": - criterias.append(query.Term("has_type", self.evalBoolean(v) is False)) - elif k == "created__date__lt": + elif query_filter == "id__all": + for object_id in value.split(","): + criterias.append(query.Term(f"{field}_id", object_id)) + elif query_filter == "date__lt": criterias.append( - query.DateRange("created", start=None, end=isoparse(v)), + query.DateRange(field, start=None, end=isoparse(value)), ) - elif k == "created__date__gt": + elif query_filter == "date__gt": criterias.append( - query.DateRange("created", start=isoparse(v), end=None), + query.DateRange(field, start=isoparse(value), end=None), + ) + elif query_filter == "icontains": + criterias.append( + query.Term(field, value), + ) + elif query_filter == "istartswith": + criterias.append( + query.Prefix(field, value), ) - elif k == "added__date__gt": - criterias.append(query.DateRange("added", start=isoparse(v), end=None)) - elif k == "added__date__lt": - criterias.append(query.DateRange("added", start=None, end=isoparse(v))) - elif k == "storage_path__id": - criterias.append(query.Term("path_id", v)) - elif k == "storage_path__id__in": - storage_paths_in = [] - for storage_path_id in v.split(","): - storage_paths_in.append(query.Term("path_id", storage_path_id)) - criterias.append(query.Or(storage_paths_in)) - elif k == "storage_path__id__none": - for storage_path_id in v.split(","): - criterias.append(query.Not(query.Term("path_id", storage_path_id))) - elif k == "storage_path__isnull": - criterias.append(query.Term("has_path", self.evalBoolean(v) is False)) - elif k == "owner__isnull": - criterias.append(query.Term("has_owner", self.evalBoolean(v) is False)) - elif k == "owner__id": - criterias.append(query.Term("owner_id", v)) - elif k == "owner__id__in": - owners_in = [] - for owner_id in v.split(","): - owners_in.append(query.Term("owner_id", owner_id)) - criterias.append(query.Or(owners_in)) - elif k == "owner__id__none": - for owner_id in v.split(","): - criterias.append(query.Not(query.Term("owner_id", owner_id))) user_criterias = get_permissions_criterias( user=self.user, diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 40765443b..4a39d98ea 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -346,7 +346,7 @@ def cleanup_document_deletion(sender, instance, using, **kwargs): logger.debug(f"Deleted file {filename}.") except OSError as e: logger.warning( - f"While deleting document {str(instance)}, the file " + f"While deleting document {instance!s}, the file " f"{filename} could not be deleted: {e}", ) @@ -369,13 +369,13 @@ class CannotMoveFilesException(Exception): def validate_move(instance, old_path, new_path): if not os.path.isfile(old_path): # Can't do anything if the old file does not exist anymore. - logger.fatal(f"Document {str(instance)}: File {old_path} has gone.") + logger.fatal(f"Document {instance!s}: File {old_path} has gone.") raise CannotMoveFilesException if os.path.isfile(new_path): # Can't do anything if the new file already exists. Skip updating file. logger.warning( - f"Document {str(instance)}: Cannot rename file " + f"Document {instance!s}: Cannot rename file " f"since target path {new_path} already exists.", ) raise CannotMoveFilesException diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 1603a1359..d320875a0 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -116,7 +116,7 @@ def consume_file( {"type": "status_update", "data": payload}, ) except ConnectionError as e: - logger.warning(f"ConnectionError on status send: {str(e)}") + logger.warning(f"ConnectionError on status send: {e!s}") # consuming stops here, since the original document with # the barcodes has been split and will be consumed separately diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 96db370ae..90684b338 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -420,6 +420,74 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): results = response.data["results"] self.assertEqual(len(results), 0) + def test_document_checksum_filter(self): + Document.objects.create( + title="none1", + checksum="A", + mime_type="application/pdf", + ) + doc2 = Document.objects.create( + title="none2", + checksum="B", + mime_type="application/pdf", + ) + Document.objects.create( + title="none3", + checksum="C", + mime_type="application/pdf", + ) + + response = self.client.get("/api/documents/?checksum__iexact=B") + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 1) + self.assertEqual(results[0]["id"], doc2.id) + + response = self.client.get("/api/documents/?checksum__iexact=X") + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 0) + + def test_document_original_filename_filter(self): + doc1 = Document.objects.create( + title="none1", + checksum="A", + mime_type="application/pdf", + original_filename="docA.pdf", + ) + doc2 = Document.objects.create( + title="none2", + checksum="B", + mime_type="application/pdf", + original_filename="docB.pdf", + ) + doc3 = Document.objects.create( + title="none3", + checksum="C", + mime_type="application/pdf", + original_filename="docC.pdf", + ) + + response = self.client.get("/api/documents/?original_filename__iexact=DOCa.pdf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 1) + self.assertEqual(results[0]["id"], doc1.id) + + response = self.client.get("/api/documents/?original_filename__iexact=docx.pdf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 0) + + response = self.client.get("/api/documents/?original_filename__istartswith=dOc") + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 3) + self.assertCountEqual( + [results[0]["id"], results[1]["id"], results[2]["id"]], + [doc1.id, doc2.id, doc3.id], + ) + def test_documents_title_content_filter(self): doc1 = Document.objects.create( title="title A", @@ -1086,17 +1154,19 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): checksum="4", created=timezone.make_aware(datetime.datetime(2020, 7, 13)), content="test", + original_filename="doc4.pdf", ) d4.tags.add(t2) d5 = Document.objects.create( checksum="5", added=timezone.make_aware(datetime.datetime(2020, 7, 13)), content="test", + original_filename="doc5.pdf", ) Document.objects.create(checksum="6", content="test2") d7 = Document.objects.create(checksum="7", storage_path=sp, content="test") d8 = Document.objects.create( - checksum="8", + checksum="foo", correspondent=c2, document_type=dt2, storage_path=sp2, @@ -1239,6 +1309,16 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): ), ) + self.assertEqual( + search_query("&checksum__icontains=foo"), + [d8.id], + ) + + self.assertCountEqual( + search_query("&original_filename__istartswith=doc"), + [d4.id, d5.id], + ) + def test_search_filtering_respect_owner(self): """ GIVEN: @@ -2514,11 +2594,25 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase): def setUp(self): super().setUp() self.test_user = User.objects.create_superuser(username="test") + self.test_user.first_name = "Test" + self.test_user.last_name = "User" + self.test_user.save() self.client.force_authenticate(user=self.test_user) def test_api_get_ui_settings(self): response = self.client.get(self.ENDPOINT, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( + response.data["user"], + { + "id": self.test_user.id, + "username": self.test_user.username, + "is_superuser": True, + "groups": [], + "first_name": self.test_user.first_name, + "last_name": self.test_user.last_name, + }, + ) self.assertDictEqual( response.data["settings"], { diff --git a/src/documents/tests/test_barcodes.py b/src/documents/tests/test_barcodes.py index 838671256..eda97554d 100644 --- a/src/documents/tests/test_barcodes.py +++ b/src/documents/tests/test_barcodes.py @@ -12,6 +12,7 @@ from documents.barcodes import BarcodeReader from documents.consumer import ConsumerError from documents.data_models import ConsumableDocument from documents.data_models import DocumentSource +from documents.models import Document from documents.tests.utils import DirectoriesMixin from documents.tests.utils import FileSystemAssertsMixin @@ -764,7 +765,7 @@ class TestAsnBarcode(DirectoriesMixin, TestCase): self.assertEqual(reader.pdf_file, test_file) self.assertEqual(asn, 123) - def test_scan_file_for_asn_not_existing(self): + def test_scan_file_for_asn_not_found(self): """ GIVEN: - PDF without an ASN barcode @@ -781,6 +782,49 @@ class TestAsnBarcode(DirectoriesMixin, TestCase): self.assertEqual(reader.pdf_file, test_file) self.assertEqual(asn, None) + @override_settings(CONSUMER_ENABLE_ASN_BARCODE=True) + def test_scan_file_for_asn_already_exists(self): + """ + GIVEN: + - PDF with an ASN barcode + - ASN value already exists + WHEN: + - File is scanned for barcodes + THEN: + - ASN is retrieved from the document + - Consumption fails + """ + + Document.objects.create( + title="WOW", + content="the content", + archive_serial_number=123, + checksum="456", + mime_type="application/pdf", + ) + + test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-123.pdf" + + dst = settings.SCRATCH_DIR / "barcode-39-asn-123.pdf" + shutil.copy(test_file, dst) + + with mock.patch("documents.consumer.Consumer._send_progress"): + with self.assertRaises(ConsumerError) as cm, self.assertLogs( + "paperless.consumer", + level="ERROR", + ) as logs_cm: + tasks.consume_file( + ConsumableDocument( + source=DocumentSource.ConsumeFolder, + original_file=dst, + ), + None, + ) + self.assertIn("Not consuming barcode-39-asn-123.pdf", str(cm.exception)) + error_str = logs_cm.output[0] + expected_str = "ERROR:paperless.consumer:Not consuming barcode-39-asn-123.pdf: Given ASN already exists!" + self.assertEqual(expected_str, error_str) + def test_scan_file_for_asn_barcode_invalid(self): """ GIVEN: diff --git a/src/documents/tests/test_delayedquery.py b/src/documents/tests/test_delayedquery.py new file mode 100644 index 000000000..962df7192 --- /dev/null +++ b/src/documents/tests/test_delayedquery.py @@ -0,0 +1,219 @@ +from dateutil.parser import isoparse +from django.test import TestCase +from whoosh import query + +from documents.index import DelayedQuery +from documents.index import get_permissions_criterias +from documents.models import User + + +class TestDelayedQuery(TestCase): + def setUp(self): + super().setUp() + # all tests run without permission criteria, so has_no_owner query will always + # be appended. + self.has_no_owner = query.Or([query.Term("has_owner", False)]) + + def _get_testset__id__in(self, param, field): + return ( + {f"{param}__id__in": "42,43"}, + query.And( + [ + query.Or( + [ + query.Term(f"{field}_id", "42"), + query.Term(f"{field}_id", "43"), + ], + ), + self.has_no_owner, + ], + ), + ) + + def _get_testset__id__none(self, param, field): + return ( + {f"{param}__id__none": "42,43"}, + query.And( + [ + query.Not(query.Term(f"{field}_id", "42")), + query.Not(query.Term(f"{field}_id", "43")), + self.has_no_owner, + ], + ), + ) + + def test_get_permission_criteria(self): + # tests contains touples of user instances and the expected filter + tests = ( + (None, [query.Term("has_owner", False)]), + (User(42, username="foo", is_superuser=True), []), + ( + User(42, username="foo", is_superuser=False), + [ + query.Term("has_owner", False), + query.Term("owner_id", 42), + query.Term("viewer_id", "42"), + ], + ), + ) + for user, expected in tests: + self.assertEqual(get_permissions_criterias(user), expected) + + def test_no_query_filters(self): + dq = DelayedQuery(None, {}, None, None) + self.assertEqual(dq._get_query_filter(), self.has_no_owner) + + def test_date_query_filters(self): + def _get_testset(param: str): + date_str = "1970-01-01T02:44" + date_obj = isoparse(date_str) + return ( + ( + {f"{param}__date__lt": date_str}, + query.And( + [ + query.DateRange(param, start=None, end=date_obj), + self.has_no_owner, + ], + ), + ), + ( + {f"{param}__date__gt": date_str}, + query.And( + [ + query.DateRange(param, start=date_obj, end=None), + self.has_no_owner, + ], + ), + ), + ) + + query_params = ["created", "added"] + for param in query_params: + for params, expected in _get_testset(param): + dq = DelayedQuery(None, params, None, None) + got = dq._get_query_filter() + self.assertCountEqual(got, expected) + + def test_is_tagged_query_filter(self): + tests = ( + ("True", True), + ("true", True), + ("1", True), + ("False", False), + ("false", False), + ("0", False), + ("foo", False), + ) + for param, expected in tests: + dq = DelayedQuery(None, {"is_tagged": param}, None, None) + self.assertEqual( + dq._get_query_filter(), + query.And([query.Term("has_tag", expected), self.has_no_owner]), + ) + + def test_tags_query_filters(self): + # tests contains touples of query_parameter dics and the expected whoosh query + param = "tags" + field, _ = DelayedQuery.param_map[param] + tests = ( + ( + {f"{param}__id__all": "42,43"}, + query.And( + [ + query.Term(f"{field}_id", "42"), + query.Term(f"{field}_id", "43"), + self.has_no_owner, + ], + ), + ), + # tags does not allow __id + ( + {f"{param}__id": "42"}, + self.has_no_owner, + ), + # tags does not allow __isnull + ( + {f"{param}__isnull": "true"}, + self.has_no_owner, + ), + self._get_testset__id__in(param, field), + self._get_testset__id__none(param, field), + ) + + for params, expected in tests: + dq = DelayedQuery(None, params, None, None) + got = dq._get_query_filter() + self.assertCountEqual(got, expected) + + def test_generic_query_filters(self): + def _get_testset(param: str): + field, _ = DelayedQuery.param_map[param] + return ( + ( + {f"{param}__id": "42"}, + query.And( + [ + query.Term(f"{field}_id", "42"), + self.has_no_owner, + ], + ), + ), + self._get_testset__id__in(param, field), + self._get_testset__id__none(param, field), + ( + {f"{param}__isnull": "true"}, + query.And( + [ + query.Term(f"has_{field}", False), + self.has_no_owner, + ], + ), + ), + ( + {f"{param}__isnull": "false"}, + query.And( + [ + query.Term(f"has_{field}", True), + self.has_no_owner, + ], + ), + ), + ) + + query_params = ["correspondent", "document_type", "storage_path", "owner"] + for param in query_params: + for params, expected in _get_testset(param): + dq = DelayedQuery(None, params, None, None) + got = dq._get_query_filter() + self.assertCountEqual(got, expected) + + def test_char_query_filter(self): + def _get_testset(param: str): + return ( + ( + {f"{param}__icontains": "foo"}, + query.And( + [ + query.Term(f"{param}", "foo"), + self.has_no_owner, + ], + ), + ), + ( + {f"{param}__istartswith": "foo"}, + query.And( + [ + query.Prefix(f"{param}", "foo"), + self.has_no_owner, + ], + ), + ), + ) + + query_params = ["checksum", "original_filename"] + for param in query_params: + for params, expected in _get_testset(param): + dq = DelayedQuery(None, params, None, None) + got = dq._get_query_filter() + self.assertCountEqual(got, expected) diff --git a/src/documents/tests/utils.py b/src/documents/tests/utils.py index fbde3345c..483d3b12d 100644 --- a/src/documents/tests/utils.py +++ b/src/documents/tests/utils.py @@ -105,6 +105,20 @@ class FileSystemAssertsMixin: def assertIsNotDir(self, path: Union[PathLike, str]): self.assertFalse(Path(path).resolve().is_dir(), f"Dir does exist: {path}") + def assertFilesEqual( + self, + path1: Union[PathLike, str], + path2: Union[PathLike, str], + ): + path1 = Path(path1) + path2 = Path(path2) + import hashlib + + hash1 = hashlib.sha256(path1.read_bytes()).hexdigest() + hash2 = hashlib.sha256(path2.read_bytes()).hexdigest() + + self.assertEqual(hash1, hash2, "File SHA256 mismatch") + class ConsumerProgressMixin: def setUp(self) -> None: diff --git a/src/documents/views.py b/src/documents/views.py index db25e6934..cd69095fe 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -519,7 +519,7 @@ class DocumentViewSet( try: return Response(self.getNotes(doc)) except Exception as e: - logger.warning(f"An error occurred retrieving notes: {str(e)}") + logger.warning(f"An error occurred retrieving notes: {e!s}") return Response( {"error": "Error retreiving notes, check logs for more detail."}, ) @@ -538,7 +538,7 @@ class DocumentViewSet( return Response(self.getNotes(doc)) except Exception as e: - logger.warning(f"An error occurred saving note: {str(e)}") + logger.warning(f"An error occurred saving note: {e!s}") return Response( { "error": "Error saving note, check logs for more detail.", @@ -628,7 +628,7 @@ class UnifiedSearchViewSet(DocumentViewSet): except NotFound: raise except Exception as e: - logger.warning(f"An error occurred listing search results: {str(e)}") + logger.warning(f"An error occurred listing search results: {e!s}") return HttpResponseBadRequest( "Error listing search results, check logs for more detail.", ) @@ -699,7 +699,7 @@ class BulkEditView(GenericAPIView): result = method(documents, **parameters) return Response({"result": result}) except Exception as e: - logger.warning(f"An error occurred performing bulk edit: {str(e)}") + logger.warning(f"An error occurred performing bulk edit: {e!s}") return HttpResponseBadRequest( "Error performing bulk edit, check logs for more detail.", ) @@ -1028,16 +1028,23 @@ class UiSettingsView(GenericAPIView): ui_settings["update_checking"] = { "backend_setting": settings.ENABLE_UPDATE_CHECK, } + user_resp = { + "id": user.id, + "username": user.username, + "is_superuser": user.is_superuser, + "groups": list(user.groups.values_list("id", flat=True)), + } + + if len(user.first_name) > 0: + user_resp["first_name"] = user.first_name + if len(user.last_name) > 0: + user_resp["last_name"] = user.last_name + # strip . roles = map(lambda perm: re.sub(r"^\w+.", "", perm), user.get_all_permissions()) return Response( { - "user": { - "id": user.id, - "username": user.username, - "is_superuser": user.is_superuser, - "groups": user.groups.values_list("id", flat=True), - }, + "user": user_resp, "settings": ui_settings, "permissions": roles, }, diff --git a/src/paperless/settings.py b/src/paperless/settings.py index d3c239b43..ab33e6b1a 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -476,69 +476,82 @@ CSRF_COOKIE_NAME = f"{COOKIE_PREFIX}csrftoken" SESSION_COOKIE_NAME = f"{COOKIE_PREFIX}sessionid" LANGUAGE_COOKIE_NAME = f"{COOKIE_PREFIX}django_language" + ############################################################################### # Database # ############################################################################### - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(DATA_DIR, "db.sqlite3"), - "OPTIONS": {}, - }, -} - -if os.getenv("PAPERLESS_DBHOST"): - # Have sqlite available as a second option for management commands - # This is important when migrating to/from sqlite - DATABASES["sqlite"] = DATABASES["default"].copy() - - DATABASES["default"] = { - "HOST": os.getenv("PAPERLESS_DBHOST"), - "NAME": os.getenv("PAPERLESS_DBNAME", "paperless"), - "USER": os.getenv("PAPERLESS_DBUSER", "paperless"), - "PASSWORD": os.getenv("PAPERLESS_DBPASS", "paperless"), - "OPTIONS": {}, +def _parse_db_settings() -> Dict: + databases = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(DATA_DIR, "db.sqlite3"), + "OPTIONS": {}, + }, } - if os.getenv("PAPERLESS_DBPORT"): - DATABASES["default"]["PORT"] = os.getenv("PAPERLESS_DBPORT") + if os.getenv("PAPERLESS_DBHOST"): + # Have sqlite available as a second option for management commands + # This is important when migrating to/from sqlite + databases["sqlite"] = databases["default"].copy() - # Leave room for future extensibility - if os.getenv("PAPERLESS_DBENGINE") == "mariadb": - engine = "django.db.backends.mysql" - options = { - "read_default_file": "/etc/mysql/my.cnf", - "charset": "utf8mb4", - "ssl": { - "ssl_mode": os.getenv("PAPERLESS_DBSSLMODE", "PREFERRED"), - "ca": os.getenv("PAPERLESS_DBSSLROOTCERT", None), - "cert": os.getenv("PAPERLESS_DBSSLCERT", None), - "key": os.getenv("PAPERLESS_DBSSLKEY", None), - }, + databases["default"] = { + "HOST": os.getenv("PAPERLESS_DBHOST"), + "NAME": os.getenv("PAPERLESS_DBNAME", "paperless"), + "USER": os.getenv("PAPERLESS_DBUSER", "paperless"), + "PASSWORD": os.getenv("PAPERLESS_DBPASS", "paperless"), + "OPTIONS": {}, } + if os.getenv("PAPERLESS_DBPORT"): + databases["default"]["PORT"] = os.getenv("PAPERLESS_DBPORT") - # Silence Django error on old MariaDB versions. - # VARCHAR can support > 255 in modern versions - # https://docs.djangoproject.com/en/4.1/ref/checks/#database - # https://mariadb.com/kb/en/innodb-system-variables/#innodb_large_prefix - SILENCED_SYSTEM_CHECKS = ["mysql.W003"] + # Leave room for future extensibility + if os.getenv("PAPERLESS_DBENGINE") == "mariadb": + engine = "django.db.backends.mysql" + options = { + "read_default_file": "/etc/mysql/my.cnf", + "charset": "utf8mb4", + "ssl": { + "ssl_mode": os.getenv("PAPERLESS_DBSSLMODE", "PREFERRED"), + "ca": os.getenv("PAPERLESS_DBSSLROOTCERT", None), + "cert": os.getenv("PAPERLESS_DBSSLCERT", None), + "key": os.getenv("PAPERLESS_DBSSLKEY", None), + }, + } - else: # Default to PostgresDB - engine = "django.db.backends.postgresql_psycopg2" - options = { - "sslmode": os.getenv("PAPERLESS_DBSSLMODE", "prefer"), - "sslrootcert": os.getenv("PAPERLESS_DBSSLROOTCERT", None), - "sslcert": os.getenv("PAPERLESS_DBSSLCERT", None), - "sslkey": os.getenv("PAPERLESS_DBSSLKEY", None), - } + else: # Default to PostgresDB + engine = "django.db.backends.postgresql_psycopg2" + options = { + "sslmode": os.getenv("PAPERLESS_DBSSLMODE", "prefer"), + "sslrootcert": os.getenv("PAPERLESS_DBSSLROOTCERT", None), + "sslcert": os.getenv("PAPERLESS_DBSSLCERT", None), + "sslkey": os.getenv("PAPERLESS_DBSSLKEY", None), + } - DATABASES["default"]["ENGINE"] = engine - DATABASES["default"]["OPTIONS"].update(options) + databases["default"]["ENGINE"] = engine + databases["default"]["OPTIONS"].update(options) -if os.getenv("PAPERLESS_DB_TIMEOUT") is not None: - DATABASES["default"]["OPTIONS"].update( - {"timeout": float(os.getenv("PAPERLESS_DB_TIMEOUT"))}, - ) + if os.getenv("PAPERLESS_DB_TIMEOUT") is not None: + if databases["default"]["ENGINE"] == "django.db.backends.sqlite3": + databases["default"]["OPTIONS"].update( + {"timeout": int(os.getenv("PAPERLESS_DB_TIMEOUT"))}, + ) + else: + databases["default"]["OPTIONS"].update( + {"connect_timeout": int(os.getenv("PAPERLESS_DB_TIMEOUT"))}, + ) + databases["sqlite"]["OPTIONS"].update( + {"timeout": int(os.getenv("PAPERLESS_DB_TIMEOUT"))}, + ) + return databases + + +DATABASES = _parse_db_settings() + +if os.getenv("PAPERLESS_DBENGINE") == "mariadb": + # Silence Django error on old MariaDB versions. + # VARCHAR can support > 255 in modern versions + # https://docs.djangoproject.com/en/4.1/ref/checks/#database + # https://mariadb.com/kb/en/innodb-system-variables/#innodb_large_prefix + SILENCED_SYSTEM_CHECKS = ["mysql.W003"] DEFAULT_AUTO_FIELD = "django.db.models.AutoField" @@ -662,6 +675,8 @@ CELERY_WORKER_MAX_TASKS_PER_CHILD = 1 CELERY_WORKER_SEND_TASK_EVENTS = True CELERY_TASK_SEND_SENT_EVENT = True CELERY_SEND_TASK_SENT_EVENT = True +CELERY_BROKER_CONNECTION_RETRY = True +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True CELERY_TASK_TRACK_STARTED = True CELERY_TASK_TIME_LIMIT: Final[int] = __get_int("PAPERLESS_WORKER_TIMEOUT", 1800) diff --git a/src/paperless/tests/test_settings.py b/src/paperless/tests/test_settings.py index 6b69765dd..41abb0a50 100644 --- a/src/paperless/tests/test_settings.py +++ b/src/paperless/tests/test_settings.py @@ -6,6 +6,7 @@ from unittest import mock from celery.schedules import crontab from paperless.settings import _parse_beat_schedule +from paperless.settings import _parse_db_settings from paperless.settings import _parse_ignore_dates from paperless.settings import _parse_redis_url from paperless.settings import default_threads_per_worker @@ -291,3 +292,60 @@ class TestCeleryScheduleParsing(TestCase): {}, schedule, ) + + +class TestDBSettings(TestCase): + def test_db_timeout_with_sqlite(self): + """ + GIVEN: + - PAPERLESS_DB_TIMEOUT is set + WHEN: + - Settings are parsed + THEN: + - PAPERLESS_DB_TIMEOUT set for sqlite + """ + with mock.patch.dict( + os.environ, + { + "PAPERLESS_DB_TIMEOUT": "10", + }, + ): + databases = _parse_db_settings() + + self.assertDictEqual( + { + "timeout": 10.0, + }, + databases["default"]["OPTIONS"], + ) + + def test_db_timeout_with_not_sqlite(self): + """ + GIVEN: + - PAPERLESS_DB_TIMEOUT is set but db is not sqlite + WHEN: + - Settings are parsed + THEN: + - PAPERLESS_DB_TIMEOUT set correctly in non-sqlite db & for fallback sqlite db + """ + with mock.patch.dict( + os.environ, + { + "PAPERLESS_DBHOST": "127.0.0.1", + "PAPERLESS_DB_TIMEOUT": "10", + }, + ): + databases = _parse_db_settings() + + self.assertDictContainsSubset( + { + "connect_timeout": 10.0, + }, + databases["default"]["OPTIONS"], + ) + self.assertDictEqual( + { + "timeout": 10.0, + }, + databases["sqlite"]["OPTIONS"], + ) diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index b525ef91d..65768d3c2 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -384,6 +384,8 @@ def make_criterias(rule: MailRule, supports_gmail_labels: bool): if isinstance(rule_query, dict): if len(rule_query) or len(criterias): return AND(**rule_query, **criterias) + else: + return "ALL" else: return AND(rule_query, **criterias) @@ -542,7 +544,7 @@ class MailAccountHandler(LoggingMixin): criterias = make_criterias(rule, supports_gmail_labels) self.log.debug( - f"Rule {rule}: Searching folder with criteria {str(criterias)}", + f"Rule {rule}: Searching folder with criteria {criterias}", ) try: diff --git a/src/paperless_mail/parsers.py b/src/paperless_mail/parsers.py index ded4d6a9b..3ec3e64a0 100644 --- a/src/paperless_mail/parsers.py +++ b/src/paperless_mail/parsers.py @@ -1,18 +1,18 @@ -import os import re from html import escape -from io import BytesIO -from io import StringIO +from pathlib import Path +from typing import List -import requests +import httpx from bleach import clean from bleach import linkify from django.conf import settings from django.utils.timezone import is_naive from django.utils.timezone import make_aware from humanfriendly import format_size +from imap_tools import MailAttachment from imap_tools import MailMessage -from tika import parser +from tika_client import TikaClient from documents.parsers import DocumentParser from documents.parsers import ParseError @@ -22,33 +22,15 @@ from documents.parsers import make_thumbnail_from_pdf class MailDocumentParser(DocumentParser): """ This parser uses imap_tools to parse .eml files, generates pdf using - gotenbergs and sends the html part to a local tika server for text extraction. + Gotenberg and sends the html part to a Tika server for text extraction. """ gotenberg_server = settings.TIKA_GOTENBERG_ENDPOINT tika_server = settings.TIKA_ENDPOINT logging_name = "paperless.parsing.mail" - _parsed = None - def get_parsed(self, document_path) -> MailMessage: - if not self._parsed: - try: - with open(document_path, "rb") as eml: - self._parsed = MailMessage.from_bytes(eml.read()) - except Exception as err: - raise ParseError( - f"Could not parse {document_path}: {err}", - ) from err - if not self._parsed.from_values: - self._parsed = None - raise ParseError( - f"Could not parse {document_path}: Missing 'from'", - ) - - return self._parsed - - def get_thumbnail(self, document_path, mime_type, file_name=None): + def get_thumbnail(self, document_path: Path, mime_type: str, file_name=None): if not self.archive_path: self.archive_path = self.generate_pdf(document_path) @@ -58,11 +40,11 @@ class MailDocumentParser(DocumentParser): self.logging_group, ) - def extract_metadata(self, document_path, mime_type): + def extract_metadata(self, document_path: Path, mime_type: str): result = [] try: - mail = self.get_parsed(document_path) + mail = self.parse_file_to_message(document_path) except ParseError as e: self.log.warning( f"Error while fetching document metadata for {document_path}: {e}", @@ -106,101 +88,157 @@ class MailDocumentParser(DocumentParser): result.sort(key=lambda item: (item["prefix"], item["key"])) return result - def parse(self, document_path, mime_type, file_name=None): + def parse(self, document_path: Path, mime_type: str, file_name=None): + """ + Parses the given .eml into formatted text, based on the decoded email. + + """ + def strip_text(text: str): + """ + Reduces the spacing of the given text string + """ text = re.sub(r"\s+", " ", text) text = re.sub(r"(\n *)+", "\n", text) return text.strip() - mail = self.get_parsed(document_path) + def build_formatted_text(mail_message: MailMessage) -> str: + """ + Constructs a formatted string, based on the given email. Basically tries + to get most of the email content, included front matter, into a nice string + """ + fmt_text = f"Subject: {mail_message.subject}\n\n" + fmt_text += f"From: {mail_message.from_values.full}\n\n" + to_list = [address.full for address in mail_message.to_values] + fmt_text += f"To: {', '.join(to_list)}\n\n" + if mail_message.cc_values: + fmt_text += ( + f"CC: {', '.join(address.full for address in mail.cc_values)}\n\n" + ) + if mail_message.bcc_values: + fmt_text += ( + f"BCC: {', '.join(address.full for address in mail.bcc_values)}\n\n" + ) + if mail_message.attachments: + att = [] + for a in mail.attachments: + att.append(f"{a.filename} ({format_size(a.size, binary=True)})") + fmt_text += f"Attachments: {', '.join(att)}\n\n" - self.text = f"Subject: {mail.subject}\n\n" - self.text += f"From: {mail.from_values.full}\n\n" - self.text += f"To: {', '.join(address.full for address in mail.to_values)}\n\n" - if len(mail.cc_values) >= 1: - self.text += ( - f"CC: {', '.join(address.full for address in mail.cc_values)}\n\n" - ) - if len(mail.bcc_values) >= 1: - self.text += ( - f"BCC: {', '.join(address.full for address in mail.bcc_values)}\n\n" - ) - if len(mail.attachments) >= 1: - att = [] - for a in mail.attachments: - att.append(f"{a.filename} ({format_size(a.size, binary=True)})") + if mail.html: + fmt_text += "HTML content: " + strip_text(self.tika_parse(mail.html)) - self.text += f"Attachments: {', '.join(att)}\n\n" + fmt_text += f"\n\n{strip_text(mail.text)}" - if mail.html: - self.text += "HTML content: " + strip_text(self.tika_parse(mail.html)) + return fmt_text - self.text += f"\n\n{strip_text(mail.text)}" + self.log.debug(f"Parsing file {document_path.name} into an email") + mail = self.parse_file_to_message(document_path) + + self.log.debug("Building formatted text from email") + self.text = build_formatted_text(mail) if is_naive(mail.date): self.date = make_aware(mail.date) else: self.date = mail.date - self.archive_path = self.generate_pdf(document_path) + self.log.debug("Creating a PDF from the email") + self.archive_path = self.generate_pdf(mail) + + @staticmethod + def parse_file_to_message(filepath: Path) -> MailMessage: + """ + Parses the given .eml file into a MailMessage object + """ + try: + with filepath.open("rb") as eml: + parsed = MailMessage.from_bytes(eml.read()) + if parsed.from_values is None: + raise ParseError( + f"Could not parse {filepath}: Missing 'from'", + ) + except Exception as err: + raise ParseError( + f"Could not parse {filepath}: {err}", + ) from err + + return parsed def tika_parse(self, html: str): self.log.info("Sending content to Tika server") try: - parsed = parser.from_buffer(html, self.tika_server) + with TikaClient(tika_url=self.tika_server) as client: + parsed = client.tika.as_text.from_buffer(html, "text/html") + + if "X-TIKA:content" in parsed.data: + return parsed.data["X-TIKA:content"].strip() + return "" except Exception as err: raise ParseError( f"Could not parse content with tika server at " f"{self.tika_server}: {err}", ) from err - if parsed["content"]: - return parsed["content"] + + def generate_pdf(self, mail_message: MailMessage) -> Path: + archive_path = Path(self.tempdir) / "merged.pdf" + + mail_pdf_file = self.generate_pdf_from_mail(mail_message) + + # If no HTML content, create the PDF from the message + # Otherwise, create 2 PDFs and merge them with Gotenberg + if not mail_message.html: + archive_path.write_bytes(mail_pdf_file.read_bytes()) else: - return "" + url_merge = self.gotenberg_server + "/forms/pdfengines/merge" - def generate_pdf(self, document_path): - pdf_collection = [] - url_merge = self.gotenberg_server + "/forms/pdfengines/merge" - pdf_path = os.path.join(self.tempdir, "merged.pdf") - mail = self.get_parsed(document_path) - - pdf_collection.append(("1_mail.pdf", self.generate_pdf_from_mail(mail))) - - if not mail.html: - with open(pdf_path, "wb") as file: - file.write(pdf_collection[0][1]) - file.close() - return pdf_path - else: - pdf_collection.append( - ( - "2_html.pdf", - self.generate_pdf_from_html(mail.html, mail.attachments), - ), + pdf_of_html_content = self.generate_pdf_from_html( + mail_message.html, + mail_message.attachments, ) - files = {} - for name, content in pdf_collection: - files[name] = (name, BytesIO(content)) - headers = {} - try: - response = requests.post(url_merge, files=files, headers=headers) - response.raise_for_status() # ensure we notice bad responses - except Exception as err: - raise ParseError(f"Error while converting document to PDF: {err}") from err + pdf_collection = { + "1_mail.pdf": ("1_mail.pdf", mail_pdf_file, "application/pdf"), + "2_html.pdf": ("2_html.pdf", pdf_of_html_content, "application/pdf"), + } - with open(pdf_path, "wb") as file: - file.write(response.content) - file.close() + try: + # Open a handle to each file, replacing the tuple + for filename in pdf_collection: + file_multi_part = pdf_collection[filename] + pdf_collection[filename] = ( + file_multi_part[0], + file_multi_part[1].open("rb"), + file_multi_part[2], + ) - return pdf_path + response = httpx.post(url_merge, files=pdf_collection) + response.raise_for_status() # ensure we notice bad responses - @staticmethod - def mail_to_html(mail: MailMessage) -> StringIO: - data = {} + archive_path.write_bytes(response.content) - def clean_html(text: str): + except Exception as err: + raise ParseError( + f"Error while merging email HTML into PDF: {err}", + ) from err + finally: + for filename in pdf_collection: + file_multi_part_handle = pdf_collection[filename][1] + file_multi_part_handle.close() + + return archive_path + + def mail_to_html(self, mail: MailMessage) -> Path: + """ + Converts the given email into an HTML file, formatted + based on the given template + """ + + def clean_html(text: str) -> str: + """ + Attempts to clean, escape and linkify the given HTML string + """ if isinstance(text, list): text = "\n".join([str(e) for e in text]) if type(text) != str: @@ -211,6 +249,8 @@ class MailDocumentParser(DocumentParser): text = text.replace("\n", "
") return text + data = {} + data["subject"] = clean_html(mail.subject) if data["subject"]: data["subject_label"] = "Subject" @@ -237,27 +277,33 @@ class MailDocumentParser(DocumentParser): data["date"] = clean_html(mail.date.astimezone().strftime("%Y-%m-%d %H:%M")) data["content"] = clean_html(mail.text.strip()) - html = StringIO() - from django.template.loader import render_to_string - rendered = render_to_string("email_msg_template.html", context=data) + html_file = Path(self.tempdir) / "email_as_html.html" + html_file.write_text(render_to_string("email_msg_template.html", context=data)) - html.write(rendered) - html.seek(0) + return html_file - return html - - def generate_pdf_from_mail(self, mail): + def generate_pdf_from_mail(self, mail: MailMessage) -> Path: + """ + Creates a PDF based on the given email, using the email's values in a + an HTML template + """ url = self.gotenberg_server + "/forms/chromium/convert/html" self.log.info("Converting mail to PDF") - css_file = os.path.join(os.path.dirname(__file__), "templates/output.css") + css_file = Path(__file__).parent / "templates" / "output.css" + email_html_file = self.mail_to_html(mail) - with open(css_file, "rb") as css_handle: + print(css_file) + print(email_html_file) + + with css_file.open("rb") as css_handle, email_html_file.open( + "rb", + ) as email_html_handle: files = { - "html": ("index.html", self.mail_to_html(mail)), - "css": ("output.css", css_handle), + "html": ("index.html", email_html_handle, "text/html"), + "css": ("output.css", css_handle, "text/css"), } headers = {} data = { @@ -280,7 +326,7 @@ class MailDocumentParser(DocumentParser): data["pdfFormat"] = "PDF/A-3b" try: - response = requests.post( + response = httpx.post( url, files=files, headers=headers, @@ -289,13 +335,23 @@ class MailDocumentParser(DocumentParser): response.raise_for_status() # ensure we notice bad responses except Exception as err: raise ParseError( - f"Error while converting document to PDF: {err}", + f"Error while converting email to PDF: {err}", ) from err - return response.content + email_as_pdf_file = Path(self.tempdir) / "email_as_pdf.pdf" + email_as_pdf_file.write_bytes(response.content) + + return email_as_pdf_file + + def generate_pdf_from_html( + self, + orig_html: str, + attachments: List[MailAttachment], + ) -> Path: + """ + Generates a PDF file based on the HTML and attachments of the email + """ - @staticmethod - def transform_inline_html(html, attachments): def clean_html_script(text: str): compiled_open = re.compile(re.escape("