diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d4b67e09..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,101 +0,0 @@ -version: 2.1 - -orbs: - aws-ecr: circleci/aws-ecr@4.0.1 - aws-ecs: circleci/aws-ecs@0.0.8 - docker: circleci/docker@0.5.1 - -workflows: - build_test_deploy: - jobs: - - build_test - - # push new Docker image to Docker Hub - - docker/publish: - image: operationcode/pybot - dockerfile: docker/Dockerfile - tag: ${CIRCLE_BRANCH} - after_build: - - run: - name: Tag and Push - command: | - IMAGE_ID=operationcode/pybot - docker tag ${IMAGE_ID}:${CIRCLE_BRANCH} ${IMAGE_ID}:${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM} - docker push ${IMAGE_ID}:${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM} - requires: - - build_test - filters: - branches: - only: - - master - - staging - - - # push new Docker image to ECS - - aws-ecr/build_and_push_image: - repo: pybot - tag: '${CIRCLE_BRANCH}' - dockerfile: docker/Dockerfile - requires: - - build_test - filters: - branches: - only: - - staging - - master - - # Update ECS task and service, then replace the current one - - aws-ecs/deploy-service-update: - family: 'pybot-${CIRCLE_BRANCH}' - service-name: 'pybot-svc-${CIRCLE_BRANCH}' - cluster-name: 'python-oc-services' - container-image-name-updates: 'container=pybot,tag=${CIRCLE_BRANCH}' - verify-revision-is-deployed: true - requires: - - aws-ecr/build_and_push_image - filters: - branches: - only: - - master - - staging - -jobs: - build_test: - docker: - - image: circleci/python:3.7.1 - environment: # environment variables for primary container - PIPENV_VENV_IN_PROJECT: true - - steps: - - checkout - - run: sudo chown -R circleci:circleci /usr/local/bin - - - run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages - - - restore_cache: # ensure this step occurs *before* installing dependencies - keys: - - v1-dependencies-{{ checksum "poetry.lock" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - - run: - name: Installing dependencies - command: | - sudo pip install poetry - poetry install - - - save_cache: - key: v1-dependencies-{{ checksum "poetry.lock" }} - paths: - - "/home/circleci/.cache/pypoetry/virtualenvs" - - ".venv" - - # Run tests - - run: - name: Run Tests - command: | - mkdir test-results - poetry run pytest --junitxml=test-results/pytest/results.xml - - - store_test_results: - path: test-results \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index 4f509e52..98e01671 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -*.env \ No newline at end of file +fly.toml +.env \ No newline at end of file diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 45e50cb1..00000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length = 88 - -select = C,E,F,W,B,B950 - -max-complexity = 10 - -ignore = - E501 - F401 - W503 - -exclude = - __pycache__ - testing.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 8f436477..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -custom: https://secure.lglforms.com/form_engine/s/BRtP7QUKyHOyEYsZROsRew diff --git a/.github/workflows/megalinter.yml b/.github/workflows/megalinter.yml new file mode 100644 index 00000000..d143737f --- /dev/null +++ b/.github/workflows/megalinter.yml @@ -0,0 +1,19 @@ +--- +name: MegaLinter +"on": [push] + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + mega-lint: + name: Mega Linter + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Mega Linter + uses: oxsecurity/megalinter/flavors/python@v7 + env: + VALIDATE_ALL_CODEBASE: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index f9441aa3..7ae0dbbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ -.idea -*.env -.pytest_cache -!/docker/example.env - +### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -19,6 +15,7 @@ dist/ downloads/ eggs/ .eggs/ +lib/ lib64/ parts/ sdist/ @@ -42,7 +39,6 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ -.nox/ .coverage .coverage.* .cache @@ -77,10 +73,6 @@ target/ # Jupyter Notebook .ipynb_checkpoints -# IPython -profile_default/ -ipython_config.py - # pyenv .python-version @@ -111,8 +103,9 @@ venv.bak/ # mypy .mypy_cache/ -.dmypy.json -dmypy.json -# Pyre type checker -.pyre/ \ No newline at end of file +.idea/* +env* +fly.toml + +.ruff_cache \ No newline at end of file diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 2df5cc3e..00000000 --- a/.isort.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[settings] -line_length = 88 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 00000000..b243ccd7 --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,47 @@ +--- +DISABLE: + - CLOUDFORMATION + - CSS + - EDITORCONFIG + - JAVASCRIPT + - TEKTON +DISABLE_LINTERS: + - JSON_PRETTIER + - PYTHON_BANDIT + - PYTHON_FLAKE8 + # We use MyPy + - PYTHON_PYLINT + - PYTHON_PYRIGHT + - REPOSITORY_GITLEAKS + - SPELL_CSPELL + - SPELL_LYCHEE + - SPELL_PROSELINT +DISABLE_ERRORS_LINTERS: + - REPOSITORY_DEVSKIM + - REPOSITORY_SEMGREP +DOCKERFILE_HADOLINT_ARGUMENTS: "--ignore DL3008 --ignore DL3018 --ignore DL3013 --ignore DL3059 --ignore DL3005" +COPYPASTE_JSCPD_ARGUMENTS: + - "--ignore '**/handlers/**,**/vector*'" +MARKDOWN_MARKDOWN_LINK_CHECK_CONFIG_FILE: ".markdown-link-check-config.json" +MARKDOWN_MARKDOWN_LINK_CHECK_DISABLE_ERRORS: true +PRINT_ALL_FILES: false +PYTHON_ISORT_CONFIG_FILE: "pyproject.toml" +PYTHON_MYPY_PRE_COMMANDS: + - command: "yes | pip install types-redis types-urllib3 types-requests && mkdir .mypy_cache" + continue_on_failure: true + cwd: "workspace" +PYTHON_MYPY_ARGUMENTS: + [ + "--ignore-missing-imports", + "--follow-imports=skip", + "--strict-optional", + "--disallow-any-generics", + ] +PYTHON_MYPY_CONFIG_FILE: "pyproject.toml" +PYTHON_RUFF_CONFIG_FILE: "pyproject.toml" +REPOSITORY_DEVSKIM_ARGUMENTS: ["-g", ".mypy_cache/*"] +SHOW_ELAPSED_TIME: true +SPELL_MISSPELL_FILTER_REGEX_EXCLUDE: '(\.automation/generated|docs/descriptors)' +YAML_YAMLLINT_FILTER_REGEX_EXCLUDE: '(templates/\.mega-linter\.yml/tests/**\/tests\/**)' +YAML_PRETTIER_FILTER_REGEX_EXCLUDE: '(templates/\.mega-linter\.yml|mkdocs\.yml)' +YAML_V8R_FILTER_REGEX_EXCLUDE: '(descriptors|templates/\.mega-linter\.yml|\.codecov\.yml)' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..55fb6008 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,41 @@ +--- +repos: + - repo: https://github.com/ambv/black + rev: 23.3.0 + hooks: + - id: black + language_version: python3.10 + args: [--line-length=119] + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.277" + hooks: + - id: ruff-autofix + args: [--fix] + + - repo: local + # We do not use pre-commit/mirrors-mypy, + # as it comes with opinionated defaults + # (like --ignore-missing-imports) + # and is difficult to configure to run + # with the dependencies correctly installed. + hooks: + - id: mypy + name: mypy + entry: mypy + language: python + language_version: python3.10 + additional_dependencies: ["mypy==1.4.1"] + types: [python] + # use require_serial so that script + # is only called once per commit + require_serial: true + # Print the number of files as a sanity-check + verbose: true + args: + - --ignore-missing-imports + - --follow-imports=skip + - --install-types + - --non-interactive + - --strict-optional + - --disallow-any-generics diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..ea1e3637 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,8 @@ +--- +extends: default + +ignore: | + tests/* + +rules: + line-length: disable diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..38cd28a0 --- /dev/null +++ b/Pipfile @@ -0,0 +1,30 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +fastapi = "*" +uvicorn = {extras = ["standard"], version = "*"} +slack-bolt = "*" +requests = "*" +slack-sdk = "*" +airtable = "*" +pyairtable = "*" +pydantic = "*" +apscheduler = "*" +dailyprogrammer = "*" +aiohttp = "==3.8.1" +gunicorn = "*" + +[dev-packages] +pytest = "*" +black = "*" +pytest-vcr = "*" +pyyaml = "*" + +[requires] +python_version = "3.10" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..516f3739 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1153 @@ +{ + "_meta": { + "hash": { + "sha256": "c20eb0e07c63c8b1ba5cca805ed4e280b8e814c22842595a4658083a28d2fbcb" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiohttp": { + "hashes": [ + "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3", + "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782", + "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75", + "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf", + "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7", + "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675", + "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1", + "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785", + "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4", + "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf", + "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5", + "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15", + "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca", + "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8", + "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac", + "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8", + "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef", + "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516", + "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700", + "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2", + "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8", + "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0", + "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676", + "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad", + "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155", + "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db", + "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd", + "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091", + "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602", + "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411", + "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93", + "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd", + "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec", + "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51", + "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7", + "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17", + "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d", + "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00", + "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923", + "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440", + "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32", + "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e", + "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1", + "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724", + "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a", + "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8", + "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2", + "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33", + "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b", + "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2", + "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632", + "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b", + "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2", + "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316", + "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74", + "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96", + "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866", + "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44", + "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950", + "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa", + "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c", + "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a", + "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd", + "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd", + "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9", + "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421", + "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2", + "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922", + "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4", + "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237", + "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642", + "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578" + ], + "index": "pypi", + "version": "==3.8.1" + }, + "aiosignal": { + "hashes": [ + "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a", + "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.0" + }, + "airtable": { + "hashes": [ + "sha256:d653e1b3b92297e3f0cc4226215d43d7fa99c72f38918e9371e445abf0baa70c", + "sha256:fb667e55da3af1341e0f2946014cc29e7d0613e69f3ee20528051c4075aee75c" + ], + "index": "pypi", + "version": "==0.4.8" + }, + "anyio": { + "hashes": [ + "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6", + "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.5.0" + }, + "apscheduler": { + "hashes": [ + "sha256:5cf344ebcfbdaa48ae178c029c055cec7bc7a4a47c21e315e4d1f08bd35f2355", + "sha256:c22cb14b411a31435eb2c530dfbbec948ac63015b517087c7978adb61b574865" + ], + "index": "pypi", + "version": "==3.8.1" + }, + "asgiref": { + "hashes": [ + "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", + "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.5.0" + }, + "async-timeout": { + "hashes": [ + "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", + "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" + }, + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd", + "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455" + ], + "markers": "python_version >= '3'", + "version": "==2.0.10" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "dailyprogrammer": { + "hashes": [ + "sha256:1540a01c59b3b99a5b3f1dcd94032b639a09afae730ee7d80267a532be014949", + "sha256:32391ed029daebbc052ebc2a6c28e1ef97006338c396f2ae9958413eb1c137fc", + "sha256:5d646f22e2983b2b21486cf169f2a02faa423b63cfce30a2b985499e95a784b1" + ], + "index": "pypi", + "version": "==1.0" + }, + "decorator": { + "hashes": [ + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + ], + "markers": "python_version >= '3.5'", + "version": "==5.1.1" + }, + "fastapi": { + "hashes": [ + "sha256:dcfee92a7f9a72b5d4b7ca364bd2b009f8fc10d95ed5769be20e94f39f7e5a15", + "sha256:f0a618aff5f6942862f2d3f20f39b1c037e33314d1b8207fd1c3a2cca76dfd8c" + ], + "index": "pypi", + "version": "==0.73.0" + }, + "frozenlist": { + "hashes": [ + "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e", + "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08", + "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b", + "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486", + "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78", + "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468", + "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1", + "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953", + "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3", + "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d", + "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a", + "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141", + "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08", + "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07", + "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa", + "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa", + "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868", + "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f", + "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b", + "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b", + "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1", + "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f", + "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478", + "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58", + "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01", + "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8", + "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d", + "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676", + "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274", + "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab", + "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8", + "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24", + "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a", + "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2", + "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f", + "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f", + "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93", + "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1", + "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51", + "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846", + "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5", + "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d", + "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c", + "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e", + "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae", + "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02", + "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0", + "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b", + "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3", + "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b", + "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa", + "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a", + "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d", + "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed", + "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148", + "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9", + "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c", + "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2", + "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, + "gunicorn": { + "hashes": [ + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + ], + "index": "pypi", + "version": "==20.1.0" + }, + "h11": { + "hashes": [ + "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", + "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" + ], + "markers": "python_version >= '3.6'", + "version": "==0.13.0" + }, + "httptools": { + "hashes": [ + "sha256:04114db99605c9b56ea22a8ec4d7b1485b908128ed4f4a8f6438489c428da794", + "sha256:074afd8afdeec0fa6786cd4a1676e0c0be23dc9a017a86647efa6b695168104f", + "sha256:113816f9af7dcfc4aa71ebb5354d77365f666ecf96ac7ff2aa1d24b6bca44165", + "sha256:1a8f26327023fa1a947d36e60a0582149e182fbbc949c8a65ec8665754dbbe69", + "sha256:2119fa619a4c53311f594f25c0205d619350fcb32140ec5057f861952e9b2b4f", + "sha256:21e948034f70e47c8abfa2d5e6f1a5661f87a2cddc7bcc70f61579cc87897c70", + "sha256:32a10a5903b5bc0eb647d01cd1e95bec3bb614a9bf53f0af1e01360b2debdf81", + "sha256:3787c1f46e9722ef7f07ea5c76b0103037483d1b12e34a02c53ceca5afa4e09a", + "sha256:3f82eb106e1474c63dba36a176067e65b48385f4cecddf3616411aa5d1fbdfec", + "sha256:3f9b4856d46ba1f0c850f4e84b264a9a8b4460acb20e865ec00978ad9fbaa4cf", + "sha256:4137137de8976511a392e27bfdcf231bd926ac13d375e0414e927b08217d779e", + "sha256:4687dfc116a9f1eb22a7d797f0dc6f6e17190d406ca4e729634b38aa98044b17", + "sha256:47dba2345aaa01b87e4981e8756af441349340708d5b60712c98c55a4d28f4af", + "sha256:5a836bd85ae1fb4304f674808488dae403e136d274aa5bafd0e6ee456f11c371", + "sha256:6e676bc3bb911b11f3d7e2144b9a53600bf6b9b21e0e4437aa308e1eef094d97", + "sha256:72ee0e3fb9c6437ab3ae34e9abee67fcee6876f4f58504e3f613dd5882aafdb7", + "sha256:79717080dc3f8b1eeb7f820b9b81528acbc04be6041f323fdd97550da2062575", + "sha256:8ac842df4fc3952efa7820b277961ea55e068bbc54cb59a0820400de7ae358d8", + "sha256:9f475b642c48b1b78584bdd12a5143e2c512485664331eade9c29ef769a17598", + "sha256:b8ac7dee63af4346e02b1e6d32202e3b5b3706a9928bec6da6d7a5b066217422", + "sha256:c0ac2e0ce6733c55858932e7d37fcc7b67ba6bb23e9648593c55f663de031b93", + "sha256:c14576b737d9e6e4f2a86af04918dbe9b62f57ce8102a8695c9a382dbe405c7f", + "sha256:cdc3975db86c29817e6d13df14e037c931fc893a710fb71097777a4147090068", + "sha256:eda95634027200f4b2a6d499e7c2e7fa9b8ee57e045dfda26958ea0af27c070b" + ], + "version": "==0.3.0" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "jsonpath-rw": { + "hashes": [ + "sha256:05c471281c45ae113f6103d1268ec7a4831a2e96aa80de45edc89b11fac4fbec" + ], + "version": "==1.4.0" + }, + "jsonpath-rw-ext": { + "hashes": [ + "sha256:0947e018c4e6d46f9d04c56487793c702eb225fa252891aa4ed41a9ca26f3d84", + "sha256:a9e44e803b6d87d135b09d1e5af0db4d4cf97ba62711a80aa51c8c721980a994" + ], + "version": "==1.2.2" + }, + "multidict": { + "hashes": [ + "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", + "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", + "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", + "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", + "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", + "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", + "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", + "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", + "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", + "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", + "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", + "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", + "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", + "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", + "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", + "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", + "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", + "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", + "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", + "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", + "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", + "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", + "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", + "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", + "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", + "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", + "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", + "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", + "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", + "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", + "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", + "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", + "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", + "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", + "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", + "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", + "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", + "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", + "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", + "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", + "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", + "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", + "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", + "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", + "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", + "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", + "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", + "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", + "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", + "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", + "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", + "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", + "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", + "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", + "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", + "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", + "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", + "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", + "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.2" + }, + "pbr": { + "hashes": [ + "sha256:176e8560eaf61e127817ef93d8a844803abb27a4d4637f0ff3bb783129be2e0a", + "sha256:672d8ebee84921862110f23fcec2acea191ef58543d34dfe9ef3d9f13c31cddf" + ], + "markers": "python_version >= '2.6'", + "version": "==5.8.0" + }, + "ply": { + "hashes": [ + "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", + "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce" + ], + "version": "==3.11" + }, + "pyairtable": { + "hashes": [ + "sha256:4132af74f96e185ed04ab5bb73c1b7fcccd0b6e1578e41af149f1b9c1383788c", + "sha256:cfb5f26a2d5a75b3896428eb87def47427c3e570ac08bb61dec70927fe9b1117" + ], + "index": "pypi", + "version": "==1.0.0.post1" + }, + "pydantic": { + "hashes": [ + "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3", + "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398", + "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1", + "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65", + "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4", + "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16", + "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2", + "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c", + "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6", + "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce", + "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9", + "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3", + "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034", + "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c", + "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a", + "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77", + "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b", + "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6", + "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f", + "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721", + "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37", + "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032", + "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d", + "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed", + "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6", + "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054", + "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25", + "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46", + "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5", + "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c", + "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070", + "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1", + "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7", + "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d", + "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145" + ], + "index": "pypi", + "version": "==1.9.0" + }, + "python-dotenv": { + "hashes": [ + "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3", + "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f" + ], + "version": "==0.19.2" + }, + "pytz": { + "hashes": [ + "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", + "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" + ], + "version": "==2021.3" + }, + "pytz-deprecation-shim": { + "hashes": [ + "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6", + "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.1.0.post0" + }, + "pyyaml": { + "hashes": [ + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "version": "==6.0" + }, + "requests": { + "hashes": [ + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + ], + "index": "pypi", + "version": "==2.27.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "slack-bolt": { + "hashes": [ + "sha256:12ade47fa61f66804715e54d0b3defe968d40b96d059afce1cb3bb9b4686bd05", + "sha256:be0a65a6d295101af75f4977c617a7702de7a6397e4e972fa026bccfea5f40e8" + ], + "index": "pypi", + "version": "==1.11.2" + }, + "slack-sdk": { + "hashes": [ + "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641", + "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67" + ], + "index": "pypi", + "version": "==3.13.0" + }, + "sniffio": { + "hashes": [ + "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", + "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" + ], + "markers": "python_version >= '3.5'", + "version": "==1.2.0" + }, + "starlette": { + "hashes": [ + "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050", + "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8" + ], + "markers": "python_version >= '3.6'", + "version": "==0.17.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "tzdata": { + "hashes": [ + "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5", + "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21" + ], + "markers": "python_version >= '3.6'", + "version": "==2021.5" + }, + "tzlocal": { + "hashes": [ + "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09", + "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1" + }, + "urllib3": { + "hashes": [ + "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed", + "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.8" + }, + "uvicorn": { + "extras": [ + "standard" + ], + "hashes": [ + "sha256:60a149248181920a73b2e97aec1dacec5501618867f041a228b2519d91a62a91", + "sha256:fa166e6c3d58e23ff5a1a3543b079c7b28aa057ab1388201e4b34a49ec05da72" + ], + "index": "pypi", + "version": "==0.17.0.post1" + }, + "uvloop": { + "hashes": [ + "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450", + "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897", + "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861", + "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c", + "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805", + "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d", + "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464", + "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f", + "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9", + "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab", + "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f", + "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638", + "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64", + "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee", + "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382", + "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228" + ], + "version": "==0.16.0" + }, + "watchgod": { + "hashes": [ + "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29", + "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7" + ], + "version": "==0.7" + }, + "websockets": { + "hashes": [ + "sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657", + "sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c", + "sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485", + "sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c", + "sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5", + "sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9", + "sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d", + "sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4", + "sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f", + "sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b", + "sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053", + "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc", + "sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519", + "sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127", + "sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a", + "sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37", + "sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909", + "sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530", + "sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155", + "sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f", + "sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be", + "sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534", + "sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72", + "sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd", + "sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db", + "sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f", + "sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e", + "sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c", + "sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7", + "sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e", + "sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a", + "sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb", + "sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0", + "sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123", + "sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6", + "sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8", + "sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17", + "sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9", + "sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536", + "sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7", + "sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69", + "sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c", + "sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54", + "sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89", + "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60", + "sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d", + "sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512", + "sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58" + ], + "version": "==10.1" + }, + "yarl": { + "hashes": [ + "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac", + "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8", + "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e", + "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746", + "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98", + "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125", + "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d", + "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d", + "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986", + "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d", + "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec", + "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8", + "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee", + "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3", + "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1", + "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd", + "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b", + "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de", + "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0", + "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8", + "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6", + "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245", + "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23", + "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332", + "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1", + "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c", + "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4", + "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0", + "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8", + "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832", + "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58", + "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6", + "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1", + "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52", + "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92", + "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185", + "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d", + "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d", + "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b", + "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739", + "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05", + "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63", + "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d", + "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa", + "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913", + "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe", + "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b", + "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b", + "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656", + "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1", + "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4", + "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e", + "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63", + "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271", + "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed", + "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d", + "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda", + "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265", + "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f", + "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c", + "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba", + "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c", + "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b", + "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523", + "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a", + "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef", + "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95", + "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72", + "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794", + "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41", + "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576", + "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.2" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "black": { + "hashes": [ + "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", + "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" + ], + "index": "pypi", + "version": "==21.12b0" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "multidict": { + "hashes": [ + "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", + "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", + "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", + "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", + "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", + "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", + "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", + "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", + "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", + "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", + "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", + "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", + "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", + "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", + "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", + "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", + "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", + "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", + "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", + "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", + "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", + "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", + "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", + "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", + "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", + "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", + "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", + "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", + "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", + "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", + "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", + "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", + "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", + "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", + "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", + "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", + "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", + "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", + "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", + "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", + "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", + "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", + "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", + "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", + "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", + "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", + "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", + "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", + "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", + "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", + "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", + "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", + "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", + "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", + "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", + "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", + "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", + "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", + "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca", + "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.1" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.7" + }, + "pytest": { + "hashes": [ + "sha256:8fc363e0b7407a9397e660ef81e1634e4504faaeb6ad1d2416da4c38d29a0f45", + "sha256:e1af71303d633af3376130b388e028342815cff74d2f3be4aeb22f3fd94325e6" + ], + "index": "pypi", + "version": "==7.0.0rc1" + }, + "pytest-vcr": { + "hashes": [ + "sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896", + "sha256:2f316e0539399bea0296e8b8401145c62b6f85e9066af7e57b6151481b0d6d9c" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "pyyaml": { + "hashes": [ + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "version": "==6.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "tomli": { + "hashes": [ + "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", + "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "vcrpy": { + "hashes": [ + "sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162", + "sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599" + ], + "markers": "python_version >= '3.5'", + "version": "==4.1.1" + }, + "wrapt": { + "hashes": [ + "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179", + "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096", + "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374", + "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df", + "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185", + "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785", + "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7", + "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909", + "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918", + "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33", + "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068", + "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829", + "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af", + "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79", + "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce", + "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc", + "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36", + "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade", + "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca", + "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32", + "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125", + "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e", + "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709", + "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f", + "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b", + "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb", + "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb", + "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489", + "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640", + "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb", + "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851", + "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d", + "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44", + "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13", + "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2", + "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb", + "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b", + "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9", + "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755", + "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c", + "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a", + "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf", + "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3", + "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229", + "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e", + "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de", + "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554", + "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10", + "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80", + "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056", + "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.13.3" + }, + "yarl": { + "hashes": [ + "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac", + "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8", + "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e", + "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746", + "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98", + "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125", + "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d", + "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d", + "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986", + "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d", + "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec", + "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8", + "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee", + "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3", + "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1", + "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd", + "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b", + "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de", + "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0", + "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8", + "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6", + "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245", + "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23", + "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332", + "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1", + "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c", + "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4", + "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0", + "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8", + "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832", + "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58", + "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6", + "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1", + "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52", + "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92", + "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185", + "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d", + "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d", + "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b", + "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739", + "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05", + "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63", + "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d", + "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa", + "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913", + "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe", + "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b", + "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b", + "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656", + "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1", + "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4", + "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e", + "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63", + "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271", + "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed", + "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d", + "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda", + "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265", + "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f", + "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c", + "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba", + "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c", + "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b", + "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523", + "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a", + "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef", + "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95", + "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72", + "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794", + "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41", + "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576", + "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.2" + } + } +} diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..f6315d9d --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +# Modify this Procfile to fit your needs +web: gunicorn server:app diff --git a/README.md b/README.md index d2e8a571..b9080aed 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,56 @@ -
- - Operation Code Hacktoberfest Banner - -
-
-
- -# 🎃 Hacktoberfest 🎃 - -[All the details you need](https://github.com/OperationCode/START_HERE/blob/master/README.md#-hacktoberfest-) before participating with us during Hacktoberfest. - -
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Twitter Follow](https://img.shields.io/twitter/follow/operation_code.svg?style=social&label=Follow&style=social)](https://twitter.com/operation_code) [![Code-style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://contributor-covenant.org/) -[![CircleCI](https://circleci.com/gh/OperationCode/operationcode-pybot.svg?style=svg)](https://circleci.com/gh/OperationCode/operationcode-pybot) -[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=OperationCode/operationcode-pybot)](https://dependabot.com) - -# [OperationCode-Pybot](https://github.com/OperationCode/operationcode-pybot) +# OperationCode-Pybot -OperationCode PyBot is a Python [Slack Bot](https://api.slack.com) -extending [Pyslacker's](https://pyslackers.com/) -[sir-bot-a-lot](https://github.com/pyslackers/sir-bot-a-lot-2) -framework. +OperationCode PyBot is a Python [Slack](https://api.slack.com) Bot utilizing [Slack Bolt](https://github.com/SlackAPI/bolt-python). ## Resources -* [Slack Bot Tutorial](https://www.digitalocean.com/community/tutorials/how-to-build-a-slackbot-in-python-on-ubuntu-20-04) -* [Slack Events API Framework](https://github.com/slackapi/python-slack-events-api) -* [sir-bot-a-lot](https://github.com/pyslackers/sir-bot-a-lot-2) +* [Slack Web API Methods](https://api.slack.com/methods) - used to interact with Slack beyond the built-in Slack Bolt capabilities +* [Slack Block Kit](https://api.slack.com/block-kit) - used to build the blocks used in various requests and responses +* [Slack Bolt](https://slack.dev/bolt-python/tutorial/getting-started) - the underlying framework of the bot +* [Slack Bolt API Reference](https://slack.dev/bolt-python/api-docs/slack_bolt/index.html) +* [Slack Python SDK API Reference](https://slack.dev/python-slack-sdk/api-docs/slack_sdk/index.html) ## Contributing -Bug reports and pull requests are welcome on [Github](https://github.com/OperationCode/operationcode-pybot). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. If you wish to assist, join the [\#oc-python-projects](https://operation-code.slack.com/messages/C7NJLCCMB/) rewrite to learn how to contribute. +[Bug reports](https://github.com/OperationCode/operationcode-pybot/issues) and [pull requests](https://github.com/OperationCode/operationcode-pybot/pulls) are welcome on [our Github repo](https://github.com/OperationCode/operationcode-pybot). +This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. +The best place to get assistance with OperationCode-Pybot is on [Slack](https://operationcode.org/join) in the `#oc-python-project` channel. ## Quick Start Recommended versions of tools used within the repo: -- `python@3.7` or greater (in some environments, you may need to specify version of python i.e. `python test.py` vs `python3 test.py`)) -- `git@2.17.1` or greater -- `poetry@0.12.11` or greater - - [Poetry](https://poetry.eustace.io/) is a packaging and dependency manager, similar to pip or pipenv - - Poetry provides a custom installer that can be ran via `curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python` - - Alternatively, poetry can be installed via pip/pip3 with `pip install --user poetry` or `pip3 install --user poetry` - - See https://poetry.eustace.io/docs/ - +- `python@3.10` or greater +- `pipenv@2021.5.29` or greater - [pipenv](https://github.com/pypa/pipenv) is a package manager similar to poetry that utilizes `pip` to manage project dependencies, along with creating new virtual environments +and deterministic builds ```bash -# Install dependencies (ensure poetry is already installed) -poetry install +# Ensure you have pipenv already installed +pipenv install --dev -# Run local development -poetry run python -m pybot +# Start up your virtual environment +pipenv shell -# Run testing suite -poetry run pytest +# Run the test suite +pytest -# Run formatting and linting -poetry run black . -# the next line shouldn't output anything to the terminal if it passes -poetry run flake8 -poetry run isort -rc . +# Run the code formatter +black . ``` +## How to Test Integration with SlackAPI + +In order to test the new methods and interactions you may have created already, +you'll need an "app configuration token". In order to get one of those, you'll need to create [a new +issue](https://github.com/OperationCode/operationcode-pybot/issues). Please use the `type: config token request` +label and make the title " Requests an App Config Token". For example: `Judson Stevens Requests an App Config Token`. + +Once you have created your issue, one of the maintainers of this repository will get in touch and give you your token. + + ## How to Test Integration With Slack After having developed some new feature, or having in hand what you believe is @@ -212,16 +196,26 @@ example: https://123_random_code_321.ngrok.io/slack/events -Additional setup may needed depending on the type of events pybot is subscribing to. -For example, in order to work on the app's functionality on a `team_join` event, you need to: +Additional setup may be needed depending on the type of events pybot is subscribing to. +For example, in order to work on the app's functionality on a `team_join` event, you need to: * Add `team_join` to workspace event * Make sure `greetings` channel exists and ensure the app is invited to the channel * Add necessary OAuth scopes to the app e.g. `users:read`, `chat:write`, etc. +In the section which says "Subscribe to events on behalf of users", you must add the following events: + +| Event Name | Required OAuth Scope | +|-----------------------|------------------------------| +| member_joined_channel | channels:read or groups:read | +| message.channels | channels:history | +| message.groups | groups:history | +| message.im | im:history | +| team_join | users:read | + #### Slash Commands -You can follow the instructions (and read helpful relation information) on the +You can follow the instructions (and read helpful related information) on the [Enabling interactivity with Slash Commands](https://api.slack.com/interactivity/slash-commands) page on Slack to setup pybot slash commands. When configuring a Slash command, make sure you configure the request URL to match the Base-URI that pybot is @@ -232,16 +226,15 @@ listening on followed by the text _/slack/commands_. For example: You'll use the same URI for each command. Here's a table listing of currently supported commands along with some suggested configuration text: -Command | Description | Usage Hint -------- | ----------- | ---------- -/lunch | find lunch suggestions nearby | <zip code> <distance in miles> -/mentor | request mentoring | -/mentor-volunteer | offer to mentor others | -/repeat | parrot canned messages | <10000|ask|ldap|merge|firstpr|channels|resources> -/report | report something to the admins | -/roll | roll x dice with y sides | -/ticket | submit ticket to admins | (text of ticket) - +| Command | Description | Usage Hint | +|-------------------|--------------------------------|--------------------------------------| +| /lunch | find lunch suggestions nearby | <zip code> <distance in miles> | +| /mentor | request mentoring | | +| /mentor-volunteer | offer to mentor others | | +| /repeat | parrot canned messages | <10000 |ask|ldap|merge|firstpr|channels|resources> +| /report | report something to the admins | | +| /roll | roll x dice with y sides | | +| /ticket | submit ticket to admins | (text of ticket) | **👋 IMPORTANT!** @@ -265,21 +258,30 @@ is listening on followed by the text _/slack/actions_. For example: You'll also want to make sure to configure the report message action with the following parameters: -Name | Description | Callback ID ----- | ----------- | ----------- -Report Message | Report this message to admins | report_message +| Name | Description | Callback ID | +|----------------|-------------------------------|----------------| +| Report Message | Report this message to admins | report_message | -## Known Configuration Settings +## License +This package is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). -This application has a number of environment variables that can be set when -launching to alter the behaviour of the application. The list below is an -incomplete description of a number of them. Please feel free to update this -list with more details via a PR: -Name | Description | Example ----- | ----------- | ------- -SLACK_BOT_SIGNING_SECRET | The unique signing secret used by Slack for a specific app that will be validated by pybot when inspecting an inbound API request | f3b4d774b79e0fb55af624c3f376d5b4 -BOT_USER_OAUTH_ACCESS_TOKEN | The bot user specific OAuth token used to authenticate the bot when making API requests to Slack | xoxb-800506043194-810119867738-vRvgSc3rslDUgQakFbMy3wAt -## License -This package is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). +## Notes +Option 1 - Create your own Slack workspace to use for testing. +Follow [this guide](https://slack.dev/bolt-python/tutorial/getting-started-http) + + +Start the application with WebSockets instead of HTTP for better development experience? Requires the use of the SLACK_APP_TOKEN. +Would need to set an environment variable to determine if we were in development or staging/production. + +Database to store history of events or just use logging? Probably best to use a database to store history of requests and responses? +Easier to track interactions that way. + +Utilizing FastAPI allows us to take advantage of things like Pydantic, inherent typing, models, and a better handler +for the HTTP requests themselves. + +Utilizing [FastAPI](https://fastapi.tiangolo.com/) and [Slack-Bolt](https://slack.dev/bolt-python/tutorial/getting-started-http). + +All the interactive elements of this bot were built using the Slack [Block Kit Builder](https://app.slack.com/block-kit-builder/). +The example JSON for each interactive element can be found in the `modules/slack/blocks/block_kit_examples` folder. \ No newline at end of file diff --git a/base_bot_manifest.yml b/base_bot_manifest.yml new file mode 100644 index 00000000..5470dc15 --- /dev/null +++ b/base_bot_manifest.yml @@ -0,0 +1,74 @@ +--- +_metadata: + major_version: 1 + minor_version: 1 +display_information: + name: OC-Community-Pybot +features: + bot_user: + display_name: OC-Community-Pybot + always_online: true + slash_commands: + - command: /mentor_request + url: https://oc-pybot-production.herokuapp.com/slack/events + description: Request a Mentor + should_escape: false + - command: /new_join + url: https://oc-pybot-production.herokuapp.com/slack/events + description: New join testing + should_escape: false + - command: /report + url: https://oc-pybot-production.herokuapp.com/slack/events + description: Sends a report to the moderation team + should_escape: false + - command: /join-blacks-in-tech + url: https://oc-pybot-production.herokuapp.com/slack/events + description: Sends a request to join the blacks-in-tech channel + should_escape: false + - command: /join-pride + url: https://oc-pybot-production.herokuapp.com/slack/events + description: Sends a request to join the operation-pride channel. + should_escape: false +oauth_config: + scopes: + bot: + - app_mentions:read + - channels:history + - channels:join + - channels:read + - chat:write + - chat:write.public + - commands + - emoji:read + - files:read + - groups:read + - groups:write + - im:write + - im:history + - links:read + - mpim:write + - mpim:history + - pins:read + - remote_files:read + - team.preferences:read + - team:read + - usergroups:read + - users.profile:read + - users:read + - users:read.email +settings: + event_subscriptions: + request_url: https://oc-pybot-production.herokuapp.com/slack/events + bot_events: + - app_mention + - member_joined_channel + - message.channels + - message.im + - message.mpim + - team_join + interactivity: + is_enabled: true + request_url: https://oc-pybot-production.herokuapp.com/slack/events + org_deploy_enabled: false + socket_mode_enabled: false + token_rotation_enabled: false diff --git a/bot_manifest.yml b/bot_manifest.yml new file mode 100644 index 00000000..24895b88 --- /dev/null +++ b/bot_manifest.yml @@ -0,0 +1,74 @@ +--- +_metadata: + major_version: 1 + minor_version: 1 +display_information: + name: ${BOT_NAME} +features: + bot_user: + display_name: ${BOT_NAME} + always_online: false + slash_commands: + - command: /mentor_request-${BOT_USERNAME} + url: ${NGROK_URL}/slack/events + description: Request a Mentor + should_escape: false + - command: /new_join-${BOT_USERNAME} + url: ${NGROK_URL}/slack/events + description: New join testing + should_escape: false + - command: /report-${BOT_USERNAME} + url: ${NGROK_URL}/slack/events + description: Sends a report to the moderation team + should_escape: false + - command: /join-blacks-in-tech-${BOT_USERNAME} + url: ${NGROK_URL}/slack/events + description: Sends a request to join the blacks-in-tech channel + should_escape: false + - command: /join-pride-${BOT_USERNAME} + url: ${NGROK_URL}/slack/events + description: Sends a request to join the operation-pride channel. + should_escape: false +oauth_config: + scopes: + bot: + - app_mentions:read + - channels:history + - channels:join + - channels:read + - chat:write + - chat:write.public + - commands + - emoji:read + - files:read + - groups:read + - groups:write + - im:write + - im:history + - links:read + - mpim:write + - mpim:history + - pins:read + - remote_files:read + - team.preferences:read + - team:read + - usergroups:read + - users.profile:read + - users:read + - users:read.email +settings: + event_subscriptions: + request_url: ${NGROK_URL}/slack/events + bot_events: + - app_mention + - member_joined_channel + - message.channels + - message.im + - message.mpim + - team_join + interactivity: + is_enabled: true + request_url: ${NGROK_URL}/slack/events + org_deploy_enabled: false + socket_mode_enabled: false + token_rotation_enabled: false diff --git a/build.sh b/build.sh new file mode 100644 index 00000000..6113546c --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -o errexit + +pip install --upgrade pip + +pip install 'poetry==1.3.1' + +poetry config virtualenvs.create false + +poetry install \ No newline at end of file diff --git a/data/.DS_Store b/data/.DS_Store new file mode 100644 index 00000000..d2cb4cb1 Binary files /dev/null and b/data/.DS_Store differ diff --git a/data/VA-Documents/2020-CFR-Title38-Vol-1.pdf b/data/VA-Documents/2020-CFR-Title38-Vol-1.pdf new file mode 100644 index 00000000..6fe47fdf Binary files /dev/null and b/data/VA-Documents/2020-CFR-Title38-Vol-1.pdf differ diff --git a/data/VA-Documents/2020-CFR-Title38-Vol-2.pdf b/data/VA-Documents/2020-CFR-Title38-Vol-2.pdf new file mode 100644 index 00000000..a595d1f0 Binary files /dev/null and b/data/VA-Documents/2020-CFR-Title38-Vol-2.pdf differ diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c83c854..76f87ced 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7-alpine AS base +FROM python:3.10-alpine AS base FROM base as builder @@ -9,37 +9,41 @@ ENV PYTHONUNBUFFERED 1 RUN apk update && \ apk add --no-cache build-base musl-dev python3-dev libffi-dev openssl-dev -RUN python -m venv /opt/venv -# Make sure we use the virtualenv: -ENV PATH="/opt/venv/bin:$PATH" +WORKDIR /app -COPY poetry.lock pyproject.toml ./ +COPY pyproject.toml poetry.lock ./ -RUN pip install poetry && \ - poetry config virtualenvs.create false && \ - poetry install --no-dev --no-interaction +RUN pip install --no-cache-dir --upgrade pip + +RUN pip install --no-cache-dir 'poetry==1.5.1' + +# The `dev` stage creates an image and runs the application with development settings +FROM builder as dev -# The `built-image` stage is the base for all remaining images -# Pulls all of the built dependencies from the builder stage -FROM base as built-image ENV PIP_DISABLE_PIP_VERSION_CHECK on ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 -# copy installed deps from builder image -COPY --from=builder /opt/venv /opt/venv +WORKDIR /app + +COPY .. ./ + +RUN poetry install + +ENTRYPOINT ["poetry", "run", "python3", "main.py"] -# Make sure we use the virtualenv -ENV PATH="/opt/venv/bin:$PATH" +# The `prod` stage creates an image that will run the application with production +# settings +FROM builder As prod + +WORKDIR /app + +COPY .. ./ + +ENV PYTHONDONTWRITEBYTECODE 1 -# The `app` stage is used as the base for images that don't -# need the development dependencies -FROM built-image as app +RUN poetry config virtualenvs.create false -COPY . /src -WORKDIR /src +RUN poetry install -# The `Prod` stage creates an image that will run the application using a -# production webserver and the `environments/production.py` configuration -FROM app As Prod -ENTRYPOINT ["python3", "-m", "pybot"] +ENTRYPOINT ["python3", "main.py"] diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 00000000..05886218 --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,15 @@ +--- +version: '3.9' +services: + pybot: + image: pybot:latest + container_name: pybot01 + ports: + - "8010:8010" + + ngrok: + image: wernight/ngrok:latest + environment: + - NGROK_PORT=pybot:8010 + ports: + - "4040:4040" diff --git a/docker/docker-compose.override.yml b/docker/docker-compose.override.yml new file mode 100644 index 00000000..879eb785 --- /dev/null +++ b/docker/docker-compose.override.yml @@ -0,0 +1,9 @@ +version: '3.9' +services: + pybot: + container_name: pybot-dev + build: + target: dev + context: .. + dockerfile: docker/Dockerfile + env_file: ../.env \ No newline at end of file diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml new file mode 100644 index 00000000..cf28b947 --- /dev/null +++ b/docker/docker-compose.prod.yml @@ -0,0 +1,6 @@ +version: '3.9' + +services: + pybot: + build: + target: prod \ No newline at end of file diff --git a/docker/docker-compose.weaviate.yml b/docker/docker-compose.weaviate.yml new file mode 100644 index 00000000..9939a73f --- /dev/null +++ b/docker/docker-compose.weaviate.yml @@ -0,0 +1,19 @@ +--- +version: '3.4' +services: + weaviate: + image: semitechnologies/weaviate:1.19.6 + ports: + - "8055:8080" + environment: + QUERY_DEFAULTS_LIMIT: 20 + AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true' + PERSISTENCE_DATA_PATH: "./data" + DEFAULT_VECTORIZER_MODULE: text2vec-transformers + ENABLE_MODULES: text2vec-transformers + TRANSFORMERS_INFERENCE_API: http://t2v-transformers:8055 + CLUSTER_HOSTNAME: 'node1' + t2v-transformers: + image: semitechnologies/transformers-inference:sentence-transformers-all-mpnet-base-v2 + environment: + ENABLE_CUDA: 1 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 3bb99f70..c33fc919 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,23 +1,13 @@ -version: '3.6' +--- +version: '3.9' + services: pybot: - image: pybot:latest + container_name: oc-pybot + restart: on-failure build: context: .. dockerfile: docker/Dockerfile - container_name: pybot01 - env_file: - - pybot.env - ports: - - 5000:5000 - - ngrok: - image: wernight/ngrok:latest - env_file: - - example.env - - ngrok.env - environment: - - NGROK_PORT=pybot:5000 - - NGROK_SUBDOMAIN=pybot + command: uvicorn main:api -host 0.0.0.0 --port 5001 --reload --log-level 'debug' ports: - - 4040:4040 + - "5001:5001" diff --git a/docker/example.env b/docker/example.env deleted file mode 100644 index 9d81aa32..00000000 --- a/docker/example.env +++ /dev/null @@ -1,21 +0,0 @@ -# --------------------------- # -### pybot.env ### - -# SLACK_BOT_SIGNING_SECRET= -# BOT_USER_OAUTH_ACCESS_TOKEN= - - -#~~ These are only needed if you want to override the default channel names ~~# -# COMMUNITY_CHANNEL= -# MENTOR_CHANNEL= -# TICKET_CHANNEL= - -# AIRTABLE_API_KEY= -# AIRTABLE_BASE_KEY= - - -# --------------------------- # -### ngrok.env ### - -# NGROK_AUTH= -# NGROK_SUBDOMAIN= \ No newline at end of file diff --git a/logging.yml b/logging.yml deleted file mode 100644 index 271bca56..00000000 --- a/logging.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: 1 -disable_existing_loggers: false -formatters: - simple: - format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' -filters: - messages: - (): 'pybot.customLogging.SlackMessageFilter' - -handlers: - console: - class: logging.StreamHandler - level: DEBUG - filters: - - messages - formatter: simple - stream: ext://sys.stdout -loggers: - sirbot: - level: DEBUG - propagate: true - pyback: - level: DEBUG - propagate: true - slack: - level: DEBUG - propagate: true -root: - level: DEBUG - handlers: - - console -propagate: no \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 00000000..35de8159 --- /dev/null +++ b/main.py @@ -0,0 +1,393 @@ +"""Main entry point to the application.""" + +import logging +import os +import re +from typing import Any + +import sentry_sdk +import uvicorn +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.interval import IntervalTrigger +from dotenv import load_dotenv +from fastapi import FastAPI, Request, Response +from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler +from slack_bolt.async_app import AsyncApp +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.handlers.channel_join_handler import ( + handle_channel_join_request, + handle_channel_join_request_claim, + handle_channel_join_request_claim_reset, +) +from modules.handlers.daily_programmer import handle_daily_programmer_post +from modules.handlers.greeting_handler import ( + handle_greeting_new_user_claim, + handle_new_member_join, + handle_resetting_greeting_new_user_claim, +) +from modules.handlers.mentorship_handler import ( + handle_mentor_request, + handle_mentorship_request_claim, + handle_mentorship_request_claim_reset, + handle_mentorship_request_form_submit, +) +from modules.handlers.report_handler import ( + handle_report, + handle_report_claim, + handle_report_submit, + handle_reset_report_claim, +) +from modules.models.slack_models.action_models import SlackActionRequestBody +from modules.models.slack_models.command_models import SlackCommandRequestBody +from modules.models.slack_models.event_models import ( + MemberJoinedChannelEvent, + MessageReceivedChannelEvent, +) +from modules.models.slack_models.slack_models import ( + SlackResponseBody, + SlackUserInfo, +) +from modules.models.slack_models.view_models import SlackViewRequestBody +from modules.utils.message_scheduler import schedule_messages + +load_dotenv() +logging.basicConfig(level=os.getenv("LOGGING_LEVEL", "INFO")) + +logger = logging.getLogger(__name__) + +# TODO: Change mentorship view to dynamically add descriptions for the mentorship service block - will require +# dispatching an action on select and updating the block +# TODO: Allow matching mentor to mentee based on time zone, number of mentees a mentor already has +# (will need integration with Dreami to track long term relationships) +# TODO: Integrate with current backend to grab information about the mentee after a request is sent to allow for +# better matching (could be related to time zone, zip code, etc) +# TODO: On startup, check for mentor request threads that haven't been claimed that have been open for more than +# 24 hours - if there are any, tag @mentor-coordinators in the thread +# TODO: Related to the above TODO, spawn a job when a mentorship request is received to check to make sure it's +# been claimed in 24 hours - if not, ping @mentor-coordinators +# TODO: Evaluate the above TODOs and maybe decide on a job that checks twice a day? 10 AM CDT and 7 PM CDT? Use +# Airtable instead of threads to check +# TODO: Track view closures to see when people open and then close without submission +# TODO: Use discriminators and Unions to conditionally return different types depending on a particular field - +# see https://github.com/samuelcolvin/pydantic/issues/619 +# TODO: Flush the cache using a webhook from Airtable when records are added or updated on various tables +# TODO: Check the membership of the mentors internal channel when linking for a mentorship request + +# Start an asynchronous Slack Bolt application +app = AsyncApp( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET"), +) + +""" + # Currently, the app create functionality is not released in the Slack SDK for Python + # see https://github.com/slackapi/python-slack-sdk/issues/1119 + try: + bot_name = os.getenv("BOT_NAME") + bot_username = os.getenv("BOT_USERNAME") + ngrok_url = os.getenv("NGROK_URL") + app_config_token = os.getenv("SLACK_APP_CONFIGURATION_TOKEN") + if bot_name is None or bot_username is None or ngrok_url is None or app_config_token is None: + raise "Please ensure you have all required environment variables set..." + + synchronous_app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET"), + ) + with open('bot_manifest.yml', 'r') as yaml_manifest: + yaml_obj = yaml.safe_load(yaml_manifest) + json_manifest = json.dumps(yaml_obj) + synchronous_app.client.apps_manifest_create(manifest=json_manifest) + + except Exception as generic_error: + sys.exit(1) +""" # noqa: W293 + + +# Define the application handler for the async Slack Bolt application - this adapter is specific to FastAPI +app_handler = AsyncSlackRequestHandler(app) + +# Sentry monitoring +if "SENTRY_DSN" in os.environ: + # pylint: disable=abstract-class-instantiated + sentry_sdk.init( + dsn=os.getenv("SENTRY_DSN"), + request_bodies="medium", + environment=os.getenv("RUN_ENV", "development"), + ) + +# Define the API +api = FastAPI() + +# Initialize an AsyncIOScheduler object to schedule tasks +Scheduler = AsyncIOScheduler({"apscheduler.timezone": "UTC"}) +MessageTrigger = IntervalTrigger(minutes=10) +# DailyProgrammerTrigger = IntervalTrigger(hours=24) # noqa: ERA001 + + +# Start up our job scheduler on FastAPI startup and schedule jobs as needed +@api.on_event("startup") +async def startup_event() -> None: # noqa: D103 + messages = await app.client.chat_scheduledMessages_list() + for message in messages["scheduled_messages"]: + await app.client.chat_deleteScheduledMessage( + channel=message["channel_id"], + scheduled_message_id=message["id"], + ) + job = Scheduler.add_job( + schedule_messages, + trigger=MessageTrigger, + kwargs={"async_app": app}, + ) + Scheduler.start() + logging.debug(f"Scheduled {job.name} with job_id: {job.id}") # noqa: G004 + + +# On shutdown, shutdown the scheduler service first +@api.on_event("shutdown") +async def shutdown_event(): # noqa: ANN201, D103 + Scheduler.shutdown() + + +# Currently, handled by the old Pybot and can't be handled by us without some legacy token usage +# @api.post("/pybot/api/v1/slack/invite") +# async def invite_new_user( +# email: str = Body( +# ..., example="Test@test.com", description="Email address of the user to invite" # noqa: ERA001 +# ) # noqa: ERA001, RUF100 +# ) -> None: +# await app.client.admin_users_invite( +# team_id=SlackTeam.slack_id, # noqa: ERA001 +# channel_ids=f"{SlackTeam.general_channel.id}", # noqa: ERA001 +# email=email, # noqa: ERA001 +# ) # noqa: ERA001, RUF100 + + +# The base URI for Slack to communicate with our application - this URI is used for events, commands, and any other interaction # noqa: E501 +@api.post("/slack/events") +async def base_endpoint(req: Request) -> Response: # noqa: D103 + return await app_handler.handle(req) + + +@api.get("/healthz") +async def healthz() -> Response: # noqa: D103 + return Response(status_code=200) + + +@app.command("/mentor_request") +async def handle_mentor_request_command( + context: AsyncBoltContext, + body: dict[str, Any], +) -> None: + """Handle the /mentor_request command. + + :param context: The context of the request from the Bolt framework + :param body: The body of the request + """ + logger.info("STAGE: Processing mentorship request...") + await handle_mentor_request(SlackCommandRequestBody(**body), context) + + +@app.view("mentorship_request_form_submit") +async def handle_mentorship_request_form_view_submit( + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + """Handle the submission of the mentorship request form. + + :param body: The body of the request + :param context: The context of the request from the Bolt framework + """ + logger.info("STAGE: Processing mentorship form submission...") + await handle_mentorship_request_form_submit(SlackViewRequestBody(**body), context) + + +@app.action("claim_mentorship_request") +async def handle_mentorship_request_claim_click( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing a mentorship request claim...") + await handle_mentorship_request_claim(SlackActionRequestBody(**body), context) + + +@app.action("reset_mentorship_request_claim") +async def handle_mentorship_request_claim_reset_click( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing a mentorship request claim reset...") + await handle_mentorship_request_claim_reset(SlackActionRequestBody(**body), context) + + +@app.command("/new_join") # This is used specifically for testing in staging +@app.event("member_joined_channel") +async def handle_new_member_join_event( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing new member joining...") + if "command" in body.keys() and os.getenv("RUN_ENVIRONMENT") != "production": # noqa: SIM118 + await handle_new_member_join(SlackCommandRequestBody(**body), context) + else: + await handle_new_member_join(MemberJoinedChannelEvent(**body), context) + + +@app.action("greet_new_user_claim") +async def handle_greeting_new_user_claim_action( # noqa: D103 + context: AsyncBoltContext, + body: dict[str, Any], +) -> None: + logger.info("STAGE: Processing new claim on new user for greetings...") + await handle_greeting_new_user_claim(SlackActionRequestBody(**body), context) + + +@app.action("reset_greet_new_user_claim") +async def handle_resetting_greeting_new_user_claim_action( # noqa: D103 + context: AsyncBoltContext, + body: dict[str, Any], +) -> None: + logger.info("STAGE: Resetting claim on new user greeting...") + await handle_resetting_greeting_new_user_claim( + SlackActionRequestBody(**body), + context, + ) + + +@app.command("/report") +async def handle_report_command( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing report command...") + await handle_report(body, context) + + +@app.view("report_form_submit") +async def handle_report_view_submit( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing report view submission...") + await handle_report_submit(body, context) + + +@app.action("report_claim") +async def handle_report_claim_action( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing report claim....") + await handle_report_claim( + SlackResponseBody(**body, originating_user=SlackUserInfo(**body["user"])), + context, + ) + + +@app.action("reset_report_claim") +async def handle_reset_report_claim_action( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing reset of report claim...") + await handle_reset_report_claim( + SlackResponseBody(**body, originating_user=SlackUserInfo(**body["user"])), + context, + ) + + +@app.command("/join-pride") +@app.command("/join-blacks-in-tech") +async def handle_channel_join_request_command( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Handling channel join request...") + await handle_channel_join_request(SlackCommandRequestBody(**body), context) + + +@app.action("invite_to_channel_click") +async def handle_invite_to_channel_click_action( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Handling invite button click for channel join...") + await handle_channel_join_request_claim(SlackActionRequestBody(**body), context) + + +@app.action("reset_channel_invite") +async def handle_invite_to_channel_reset_action( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Handling reset to invite button click for channel join...") + await handle_channel_join_request_claim_reset( + SlackActionRequestBody(**body), + context, + ) + + +@app.message(re.compile(r"(={2}.*={3})|(\[.*?])")) +async def handle_daily_programmer( # noqa: D103 + body: dict[str, Any], + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing daily programmer post...") + await handle_daily_programmer_post(MessageReceivedChannelEvent(**body), context) + + +@app.event("message") +async def handle_message_event(body: dict[str, Any], context: AsyncBoltContext) -> None: # noqa: D103, ARG001 + logger.info("STAGE: Processing message event...") + await context.ack() + + +@app.event("app_mention") +async def handle_app_mention_event( # noqa: D103 + body: dict[str, Any], # noqa: ARG001 + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing app mention event...") + await context.ack() + + +@app.action("oc_greeting_homepage_click") +async def handle_oc_greeting_homepage_click_action( # noqa: D103 + body: dict[str, Any], # noqa: ARG001 + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing OC greeting homepage click...") + await context.ack() + + +@app.action("oc_greeting_slack_download_click") +async def handle_oc_greeting_slack_download_click_action( # noqa: D103 + body: dict[str, Any], # noqa: ARG001 + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing OC greeting slack download click...") + await context.ack() + + +@app.action("oc_greeting_coc_click") +async def handle_oc_greeting_coc_click_action( # noqa: D103 + body: dict[str, Any], # noqa: ARG001 + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Processing OC greeting coc click...") + await context.ack() + + +if __name__ == "__main__": + if os.environ.get("RUN_ENVIRONMENT") == "development": + uvicorn.run( + "main:api", + host="0.0.0.0", # noqa: S104 + port=8010, + reload=True, + reload_dirs=["./models", "./tests"], + workers=1, + ) + else: + uvicorn.run("main:api", host="0.0.0.0", port=5001, workers=1, lifespan="on") # noqa: S104 diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/airtable/__init__.py b/modules/airtable/__init__.py new file mode 100644 index 00000000..47cc60d8 --- /dev/null +++ b/modules/airtable/__init__.py @@ -0,0 +1,26 @@ +from modules.airtable.daily_programmer_table import DailyProgrammerTable # noqa: D104 +from modules.airtable.mentorship_tables import ( + MentorshipAffiliationsTable, + MentorshipMentorsTable, + MentorshipRequestsTable, + MentorshipServicesTable, + MentorshipSkillsetsTable, +) +from modules.airtable.message_text_table import MessageTextTable +from modules.airtable.scheduled_message_table import ScheduledMessagesTable + +# General message related tables +message_text_table = MessageTextTable() + +# Scheduled message related tables +scheduled_message_table = ScheduledMessagesTable() + +# Daily Programmer related table +daily_programmer_table = DailyProgrammerTable() + +# Mentorship related tables +mentor_table = MentorshipMentorsTable() +mentorship_services_table = MentorshipServicesTable() +mentorship_skillsets_table = MentorshipSkillsetsTable() +mentorship_requests_table = MentorshipRequestsTable() +mentorship_affiliations_table = MentorshipAffiliationsTable() diff --git a/modules/airtable/daily_programmer_table.py b/modules/airtable/daily_programmer_table.py new file mode 100644 index 00000000..a9cb395e --- /dev/null +++ b/modules/airtable/daily_programmer_table.py @@ -0,0 +1,69 @@ +import logging # noqa: D100 +from typing import Any + +from pydantic import ValidationError + +from modules.airtable.shared_table import BaseAirtableTable +from modules.models.daily_programmer_models import DailyProgrammerInfo +from modules.utils import snake_case + +logger = logging.getLogger(__name__) + + +class DailyProgrammerTable(BaseAirtableTable): + """Airtable table for the daily programmer channel.""" + + def __init__(self: "DailyProgrammerTable") -> None: + """Initialize the daily programmer table.""" + super().__init__("Daily Programmer") + + @staticmethod + def parse_daily_programmer_row(row: dict[str, Any]) -> DailyProgrammerInfo: + """Parse a daily programmer row. + + :param row: The row to parse. + :return: The parsed row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return DailyProgrammerInfo( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse daily programmer row.", extra={"row": row}) + raise + + def retrieve_valid_daily_programmer_row_by_slug( + self: "DailyProgrammerTable", + slug: str, + ) -> DailyProgrammerInfo: + """Retrieve a valid daily programmer row by slug. + + :param slug: The slug to match. + :return: The parsed row. + """ + return self.parse_daily_programmer_row( + self.first( + formula=f"{{Slug}} = '{slug}'", + view="Valid", + ), + ) + + def retrieve_valid_daily_programmer_by_view( + self: "DailyProgrammerTable", + view_name: str, + ) -> dict[str, DailyProgrammerInfo]: + """Retrieve all valid daily programmer rows by view. + + :param view_name: The view name to retrieve messages from. + :return: The dictionary of messages. + """ + logger.info("STAGE: Retrieving daily programmer rows by view") + logger.info("With view_name", extra={"view_name": view_name}) + messages = {} + for row in self.all(view=view_name): + parsed_row = self.parse_daily_programmer_row(row) + messages[parsed_row.slug] = parsed_row + return messages diff --git a/modules/airtable/mentorship_tables.py b/modules/airtable/mentorship_tables.py new file mode 100644 index 00000000..547ae7eb --- /dev/null +++ b/modules/airtable/mentorship_tables.py @@ -0,0 +1,267 @@ +"""This module contains the Airtable tables for the mentorship program.""" # noqa: D404 +import logging +from functools import cached_property +from itertools import chain +from typing import Any + +from pydantic.error_wrappers import ValidationError + +from modules.airtable.shared_table import BaseAirtableTable +from modules.models.mentorship_models import ( + Mentor, + MentorshipAffiliation, + MentorshipRequest, + MentorshipService, + MentorshipSkillset, +) +from modules.utils import snake_case + +logger = logging.getLogger(__name__) + + +class MentorshipAffiliationsTable(BaseAirtableTable): + """Airtable table for the mentorship affiliations table.""" + + def __init__(self: "MentorshipAffiliationsTable") -> None: + """Initialize the mentorship affiliations table.""" + super().__init__("Affiliations") + + @cached_property + def valid_affiliations(self: "MentorshipAffiliationsTable") -> list[MentorshipAffiliation]: + """Return the valid affiliations from the table. + + :return: A list of valid affiliations. + """ + return [self.parse_affiliation_row(row) for row in self.all(view="Valid")] + + @staticmethod + def parse_affiliation_row(row: dict[str, Any]) -> MentorshipAffiliation: + """Parse an affiliation row. + + :param row: The row to parse. + :return: The parsed affiliation row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return MentorshipAffiliation( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError as validation_exception: + logger.exception("Error parsing affiliation row", extra={"row": row}) + raise validation_exception from validation_exception + + +class MentorshipMentorsTable(BaseAirtableTable): + """Table containing the mentors who have signed up to mentor others.""" + + def __init__(self: "MentorshipMentorsTable") -> None: + """Initialize the mentorship mentors table.""" + super().__init__("Mentors") + + @cached_property + def valid_mentors(self: "MentorshipMentorsTable") -> list[Mentor]: + """Returns the mentors from the table sorted by row ID. + + :return: list of mentors + """ + try: + return [self.parse_mentor_row(row) for row in self.all(view="Valid")] + except ValidationError: + logger.exception("Unable to retrieve the list of mentors") + raise + + @staticmethod + def parse_mentor_row(row: dict[str, Any]) -> Mentor: + """Parse a mentor row. + + :param row: The row to parse. + :return: The parsed row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return Mentor( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse mentor row.", extra={"row": row}) + raise + + +class MentorshipSkillsetsTable(BaseAirtableTable): + """Airtable table for the mentorship skillsets table.""" + + def __init__(self: "MentorshipSkillsetsTable") -> None: + """Initialize the mentorship skillsets table.""" + super().__init__("Skillsets") + + @cached_property + def valid_skillsets(self: "MentorshipSkillsetsTable") -> list[MentorshipSkillset]: + """Returns the skillsets from the table. + + :return: The list of skillsets. + """ + try: + return [self.parse_skillset_row(row) for row in self.all(view="Valid")] + except ValidationError: + logger.exception("Unable to retrieve the list of skillsets") + raise + + @cached_property + def mentors_by_skillsets(self: "MentorshipSkillsetsTable") -> dict[str, str]: + """Returns the mentors by skillset. + + :return: The mentors by skillset. + """ + try: + mentors_by_skillset = {} + for row in self.all(fields=["Name", "Mentors"], view="Valid"): + mentors_by_skillset[row["Name"]] = row["Mentors"] + return mentors_by_skillset # noqa: TRY300 + except Exception: + logger.exception("Issue retrieving mentor IDs by skillset") + raise + + @staticmethod + def parse_skillset_row(row: dict[str, Any]) -> MentorshipSkillset: + """Parse a skillset row. + + :param row: The row to parse. + :return: The parsed row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return MentorshipSkillset( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse skillset row.", extra={"row": row}) + raise + + def mentors_by_skillset(self: "MentorshipSkillsetsTable", skillsets_to_search: list[str]) -> set[str]: + """Retrieve mentor IDs by skillset. + + :param skillsets_to_search: The skillsets to search for. + :return: The mentor IDs that have the skillsets. + """ + logger.info("STAGE: Returning mentors by skillset...") + try: + mentors = [] + formula = [f"{{Name}} = '{skillset}'," for skillset in skillsets_to_search] + for row in self.all( + fields=["Name", "Mentors"], + view="Valid", + formula=("OR(" + "".join(formula)[:-1] + ")"), + ): + try: + mentors.append( + row["fields"]["Mentors"] if row["fields"]["Mentors"] else [], + ) + except KeyError: + logger.exception("Key error intercepted retrieving mentors by skillset", extra={"row": row}) + pass + + # Flatten the array and get unique values + return set(chain(*mentors)) + except Exception: + logger.exception( + "Issue retrieving mentor IDs with particular skillsets", + extra={"skillsets": skillsets_to_search}, + ) + raise + + +class MentorshipServicesTable(BaseAirtableTable): + """Airtable table for the mentorship services table.""" + + def __init__(self: "MentorshipServicesTable") -> None: + """Initialize the mentorship services table.""" + super().__init__("Services") + + @cached_property + def valid_services(self: "MentorshipServicesTable") -> list[MentorshipService]: + """Returns the services from the table. + + :return: The list of services from the table. + """ + try: + return [self.parse_service_row(row) for row in self.all(view="Valid")] + except ValidationError: + logger.exception("Unable to retrieve the list of services") + raise + + @staticmethod + def parse_service_row(row: dict[str, Any]) -> MentorshipService: + """Parse a service row. + + :param row: The row to parse. + :return: The parsed row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return MentorshipService( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse service row.", extra={"row": row}) + raise + + +class MentorshipRequestsTable(BaseAirtableTable): + """Airtable table for the mentorship requests table.""" + + def __init__(self: "MentorshipRequestsTable") -> None: + """Initialize the mentorship requests table.""" + super().__init__("Mentor Requests") + + @cached_property + def valid_services(self: "MentorshipRequestsTable") -> list[MentorshipRequest]: + """Returns the services from the table. + + :return: list of services from the table + """ + try: + return [self.parse_request_row(row) for row in self.all(view="Valid")] + except ValidationError: + logger.exception("Unable to retrieve the list of requests") + raise + + @staticmethod + def parse_request_row(row: dict[str, Any]) -> MentorshipRequest: + """Parse a request row. + + :param row: The row to parse. + :return: The parsed row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return MentorshipRequest( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse request row.", extra={"row": row}) + raise + + def return_record_by_slack_message_ts(self: "MentorshipRequestsTable", timestamp: str) -> MentorshipRequest: + """Return a specific record by the recorded timestamp. + + :param timestamp: The timestamp to use to find the record. + :return: The mentorship request found with the timestamp. + """ + logger.info("Returning record using timestamp", extra={"timestamp": timestamp}) + row = self.first(formula=f"{{Slack Message TS}} = '{timestamp}'") + if not row: + logger.error("Unable to find record", extra={"timestamp": timestamp}) + error_message = f"Unable to find record with timestamp {timestamp}" + raise ValueError(error_message) + logger.info("Found record", extra={"row": row}) + return self.parse_request_row(row) diff --git a/modules/airtable/message_text_table.py b/modules/airtable/message_text_table.py new file mode 100644 index 00000000..f22a39eb --- /dev/null +++ b/modules/airtable/message_text_table.py @@ -0,0 +1,67 @@ +"""Defines the message text table in Airtable.""" +import logging +from typing import Any + +from pydantic import ValidationError + +from modules.airtable.shared_table import BaseAirtableTable +from modules.models.message_text_models import MessageTextInfo +from modules.utils import snake_case + +logger = logging.getLogger(__name__) + + +class MessageTextTable(BaseAirtableTable): + """The message text table contains the various messages we send out periodically in the OC Slack workspace.""" + + def __init__(self: "MessageTextTable") -> None: + """Initialize the message text table.""" + super().__init__("Message Text") + + @staticmethod + def parse_message_text_row(row: dict[str, Any]) -> MessageTextInfo: + """Parse a message text row. + + :param row: The row to parse. + :return: The parsed message text row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return MessageTextInfo( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + except ValidationError: + logger.exception("Unable to parse message text row.", extra={"row": row}) + raise + + def retrieve_valid_message_row(self: "MessageTextTable", message_slug: str) -> MessageTextInfo: + """Retrieve all valid messages that match the given slug. + + :param message_slug: The message slug to match. + :return: The parsed message text row. + """ + return self.parse_message_text_row( + self.first( + formula=f"{{Slug}} = '{message_slug}'", + view="Valid", + ), + ) + + def retrieve_valid_messages_by_view( + self: "MessageTextTable", + view_name: str, + ) -> dict[str, MessageTextInfo]: + """Retrieve a dictionary of all valid messages by view. + + :param view_name: The view name to retrieve messages from. + :return: The dictionary of messages. + """ + logger.info("STAGE: Retrieving valid messages by view") + logger.info("With view_name", extra={"view_name": view_name}) + messages = {} + for row in self.all(view=view_name): + parsed_row = self.parse_message_text_row(row) + messages[parsed_row.slug] = parsed_row + return messages diff --git a/modules/airtable/scheduled_message_table.py b/modules/airtable/scheduled_message_table.py new file mode 100644 index 00000000..ec0c8966 --- /dev/null +++ b/modules/airtable/scheduled_message_table.py @@ -0,0 +1,43 @@ +"""Airtable table for scheduled messages.""" +import logging +from typing import Any + +from pydantic.error_wrappers import ValidationError + +from modules.airtable.shared_table import BaseAirtableTable +from modules.models.scheduled_message_models import ScheduledMessageInfo +from modules.utils import snake_case + +logger = logging.getLogger(__name__) + + +class ScheduledMessagesTable(BaseAirtableTable): + """Airtable table for scheduled messages.""" + + def __init__(self: "ScheduledMessagesTable") -> None: + """Initialize the scheduled messages table.""" + super().__init__("Scheduled Messages") + + @property + def all_valid_scheduled_messages(self: "ScheduledMessagesTable") -> list[ScheduledMessageInfo]: + """Return all valid scheduled messages.""" + return [self.parse_scheduled_message_row(row) for row in self.all(view="Valid")] + + @staticmethod + def parse_scheduled_message_row(row: dict[str, Any]) -> ScheduledMessageInfo: + """Return a parsed scheduled message row. + + :param row: The row to parse. + :return: A parsed scheduled message row. + """ + fields = {snake_case(k): v for k, v in row["fields"].items()} + try: + return ScheduledMessageInfo( + **fields, + airtable_id=row["id"], + created_at=row["createdTime"], + ) + + except ValidationError: + logger.exception("Unable to parse scheduled message row.", extra={"row": row}) + raise diff --git a/modules/airtable/shared_table.py b/modules/airtable/shared_table.py new file mode 100644 index 00000000..27bfec1a --- /dev/null +++ b/modules/airtable/shared_table.py @@ -0,0 +1,34 @@ +import os # noqa: D100 +from typing import Any + +from pyairtable import Table + +from modules.utils import table_fields + + +class BaseAirtableTable(Table): # noqa: D101 + def __init__(self, table_name: str): # noqa: ANN101, ANN204, D107 + super().__init__( + api_key=os.getenv("AIRTABLE_API_KEY"), + base_id=os.getenv("AIRTABLE_BASE_ID"), + table_name=f"{table_name}", + ) + + @property + def table_fields(self) -> list[str]: # noqa: ANN101 + """Returns snake cased columns (fields in Airtable parlance) on the table. + + :return: list of fields + :rtype: list[str] + """ + return table_fields(self) + + def update_record( # noqa: D102 + self, # noqa: ANN101 + airtable_id: str, + fields_to_update: dict[str, Any], + ) -> dict[str, Any]: + return self.update(airtable_id, fields=fields_to_update, typecast=True) + + def create_record(self, record_to_create: dict[str, Any]) -> dict[str, Any]: # noqa: ANN101, D102 + return self.create(fields=record_to_create, typecast=True) diff --git a/modules/databases/__init__.py b/modules/databases/__init__.py new file mode 100644 index 00000000..38630b76 --- /dev/null +++ b/modules/databases/__init__.py @@ -0,0 +1 @@ +"""Configuration module for databases.""" diff --git a/modules/databases/vector_database_config.py b/modules/databases/vector_database_config.py new file mode 100644 index 00000000..82c9482f --- /dev/null +++ b/modules/databases/vector_database_config.py @@ -0,0 +1,42 @@ +"""Configuration for a vector database connection.""" +import os + +import weaviate +from dotenv import load_dotenv + +load_dotenv() + +# Weaviate configuration +auth_config = weaviate.AuthApiKey(api_key=os.getenv("WEAVIATE_API_KEY", "")) + +# Create a weaviate client +weaviate_client = weaviate.Client(os.getenv("WEAVIATE_URL", "http://localhost:8015"), auth_client_secret=auth_config) + +# Create the schema +class_obj = { + "class": "TextChunk", + "description": "A chunk of text from the Federal Title Code.", + "properties": [ + {"name": "text", "dataType": ["text"]}, + {"name": "cfr_title_number", "dataType": ["int"]}, + {"name": "ingestion_date", "dataType": ["date"]}, + {"name": "index_number", "dataType": ["int"]}, + {"name": "unique_id", "dataType": ["uuid"]}, + {"name": "surrounding_context", "dataType": ["text"]}, + ], + "vectorizer": "text2vec-transformers", + "vectorIndexConfig": { + "ef": 450, + }, +} + +try: + weaviate_client.schema.create_class(class_obj) +except weaviate.exceptions.UnexpectedStatusCodeException: + print("Schema already exists.") + +try: + weaviate_client.schema.update_config("TextChunk", class_obj) +except weaviate.exceptions.UnexpectedStatusCodeException as e: + print(e) + print("Schema already updated.") diff --git a/modules/handlers/__init__.py b/modules/handlers/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/handlers/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/handlers/channel_join_handler.py b/modules/handlers/channel_join_handler.py new file mode 100644 index 00000000..9efe3e9c --- /dev/null +++ b/modules/handlers/channel_join_handler.py @@ -0,0 +1,120 @@ +"""Channel join handler module.""" +import logging +import os + +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.models.slack_models.action_models import SlackActionRequestBody +from modules.models.slack_models.command_models import SlackCommandRequestBody +from modules.slack.blocks.shared_blocks import ( + channel_join_request_action, + channel_join_request_blocks, + channel_join_request_reset_action, + channel_join_request_successful_block, +) +from modules.utils import log_to_thread, slack_team + +logger = logging.getLogger(__name__) + + +async def handle_channel_join_request( + parsed_body: SlackCommandRequestBody, + context: AsyncBoltContext, +) -> None: + """Handle the channel join request. + + :param parsed_body: The parsed body of the request. + :param context: The Slack Bolt context. + """ + logger.info("STAGE: Handling channel join command...") + await context.ack() + channel_id = "" + channel_name = "" + try: + if parsed_body.command == "/join-pride": + channel_id = slack_team.pride_channel.id + channel_name = os.getenv("PRIDE_CHANNEL_NAME", "") + if parsed_body.command == "/join-blacks-in-tech": + channel_id = slack_team.blacks_in_tech.id + channel_name = os.getenv("BLACKS_IN_TECH_CHANNEL_NAME", "") + await context.client.chat_postMessage( + channel=channel_id, + blocks=channel_join_request_blocks(parsed_body.user_name), + text="New channel join request...", + ) + await context.client.chat_postEphemeral( + channel=parsed_body.user_id, + user=parsed_body.user_id, + blocks=[channel_join_request_successful_block(channel_name)], + text=f"Your request to join {channel_name} was successful...", + ) + + except Exception as general_exception: + logger.exception("Unable to handle the channel join request") + raise general_exception from general_exception + + +async def handle_channel_join_request_claim( + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + """Handle the claim for a channel join request. + + :param parsed_body: The parsed body of the request. + :param context: The Slack Bolt context. + """ + logger.info("STAGE: Handling channel join request claim...") + await context.ack() + try: + blocks = parsed_body.message.blocks + blocks[-1] = channel_join_request_reset_action(parsed_body.user.username) + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=True, + ) + await context.respond( + text="Someone has claimed the invite request...", + blocks=blocks, + replace_original=True, + ) + + except Exception as general_exception: + logger.exception("Unable to handle the channel join request claim") + raise general_exception from general_exception + + +async def handle_channel_join_request_claim_reset( + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + """Handle the reset of claim for a channel join request. + + :param parsed_body: The parsed body of the request. + :param context: The Slack Bolt context. + """ + logger.info("STAGE: Handling channel join request claim reset...") + await context.ack() + try: + blocks = parsed_body.message.blocks + blocks[-1] = channel_join_request_action() + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=False, + ) + await context.respond( + text="Someone has reset the invite request...", + blocks=blocks, + replace_original=True, + ) + + except Exception as general_exception: + logger.exception("Unable to handle the channel join request claim reset") + raise general_exception from general_exception diff --git a/modules/handlers/daily_programmer.py b/modules/handlers/daily_programmer.py new file mode 100644 index 00000000..ff210ed9 --- /dev/null +++ b/modules/handlers/daily_programmer.py @@ -0,0 +1,96 @@ +"""Handles the daily programmer channel.""" +import logging +import re +from datetime import datetime, timezone +from difflib import SequenceMatcher + +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.airtable import daily_programmer_table +from modules.models.slack_models.event_models import MessageReceivedChannelEvent +from modules.models.slack_models.shared_models import SlackMessageInfo + +LOGGER = logging.getLogger(__name__) +MATCHING_TEXT_RATIO = 0.85 + + +async def handle_daily_programmer_post( + parsed_body: MessageReceivedChannelEvent, + context: AsyncBoltContext, +) -> None: + """Process a message that was posted to the daily programmer channel. + + :param parsed_body: The parsed body of the Slack message event. + :param context: The Slack Bolt context. + """ + await context.ack() + LOGGER.info("STAGE: Handling a daily programmer post...") + post_id, post_count = check_for_existing_post(parsed_body.event.text) + if post_id and post_count: + daily_programmer_table.update( + post_id, + {"Post Count": post_count, "Last Posted On": datetime.now(timezone.utc)}, + ) + return None # noqa: RET501 + process_daily_programmer_post_text(parsed_body.event) + + +def check_for_existing_post(text: str) -> tuple[str, int] | tuple[None, None]: + """Check for an existing daily programmer post. + + :param text: The text of the post. + :return: The existing post ID and the number of times it has been posted, if it exists. + """ + existing_posts = daily_programmer_table.all( + view="Valid", + fields=["Text", "Posted Count"], + ) + for post in existing_posts: + if SequenceMatcher(None, post["fields"]["Text"], text).ratio() > MATCHING_TEXT_RATIO: + LOGGER.info("Found matching post", extra={"post": post}) + return post["id"], int(post["fields"]["Posted Count"]) + return None, None + + +def process_daily_programmer_post_text(body: SlackMessageInfo) -> None: + """Process a post to the daily programming channel. + + :param body: The body of the Slack message. + """ + LOGGER.info("STAGE: Processing a daily programmer post text...") + # Posts to the daily programmer channel should be in the format: + # ==[Name]== + title = re.search(r"(={2,3}.*={2,3})", body.text) + if title: + LOGGER.info("Found a daily programmer post title...") + name = re.search(r"(\[.*?])", body.text) + if name: + try: + daily_programmer_table.create_record( + { + "Name": name[0].replace("[", "").replace("]", "").replace("*", ""), + "Text": body.text[name.span()[1] + 1 :], + "Initially Posted On": str( + datetime.fromtimestamp(float(body.ts), timezone.utc), + ), + "Last Posted On": str( + datetime.fromtimestamp(float(body.ts), timezone.utc), + ), + "Posted Count": 1, + "Initial Slack TS": body.ts, + "Blocks": body.blocks, + }, + ) + except Exception as general_error: + LOGGER.exception( + "Unable to create new daily programmer entry", + ) + raise general_error from general_error + return + LOGGER.warning( + "Unable to create new daily programmer entry due to not finding the name...", + ) + return + LOGGER.warning( + "Unable to create new daily programmer entry due to not finding the title...", + ) diff --git a/modules/handlers/greeting_handler.py b/modules/handlers/greeting_handler.py new file mode 100644 index 00000000..963b185b --- /dev/null +++ b/modules/handlers/greeting_handler.py @@ -0,0 +1,110 @@ +import re # noqa: D100 +from datetime import datetime, timedelta, timezone +from typing import Union + +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.models.slack_models.action_models import SlackActionRequestBody +from modules.models.slack_models.command_models import SlackCommandRequestBody +from modules.models.slack_models.event_models import MemberJoinedChannelEvent +from modules.slack.blocks.greeting_blocks import ( + greeting_block_button, + greeting_block_claimed_button, + initial_greet_user_blocks, +) +from modules.slack.blocks.new_join_blocks import ( + new_join_delayed_welcome_blocks, + new_join_immediate_welcome_blocks, +) +from modules.utils import get_slack_user_by_id, log_to_thread, slack_team + + +async def handle_new_member_join( # noqa: D103 + parsed_body: Union[MemberJoinedChannelEvent, SlackCommandRequestBody], # noqa: UP007 + context: AsyncBoltContext, +) -> None: + await context.ack() + user = None + if isinstance(parsed_body, MemberJoinedChannelEvent): + user = await get_slack_user_by_id(context.client, parsed_body.user) + elif isinstance(parsed_body, SlackCommandRequestBody): + user = await get_slack_user_by_id(context.client, parsed_body.user_id) + await context.client.chat_postMessage( + channel=slack_team.greetings_channel.id, + blocks=initial_greet_user_blocks(user), + text="A new member has joined!", + ) + # Add one minute to the current timestamp + immediate_message_timestamp = datetime.now(timezone.utc).timestamp() + 60 + await context.client.chat_scheduleMessage( + channel=user.id, + user=user.id, + post_at=int(immediate_message_timestamp), + text="Welcome to Operation Code Slack!", + blocks=new_join_immediate_welcome_blocks(user.name), + unfurl_links=False, + unfurl_media=False, + ) + # Schedule the delayed message for the next day at 1600 UTC (10 AM CST/CDT) + # This could be in two days, by popular measure, if UTC has already rolled over midnight + delayed_message_timestamp = ( + (datetime.now(timezone.utc) + timedelta(days=1)).replace(hour=16, minute=00).timestamp() + ) + await context.client.chat_scheduleMessage( + channel=user.id, + user=user.id, + post_at=int(delayed_message_timestamp), + text="We're happy to have you at Operation Code!", + blocks=new_join_delayed_welcome_blocks(), + unfurl_media=False, + unfurl_links=False, + ) + + +async def handle_greeting_new_user_claim( # noqa: D103 + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + await context.ack() + original_blocks = parsed_body.message.blocks + original_blocks[-1] = greeting_block_claimed_button(parsed_body.user.username) + modified_blocks = original_blocks + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=True, + ) + await context.respond( + text="Modified the claim to greet the new user...", + blocks=modified_blocks, + replace_original=True, + ) + + +async def handle_resetting_greeting_new_user_claim( # noqa: D103 + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + await context.ack() + original_blocks = parsed_body.message.blocks + # Extract out the username of the new user (the user we are greeting) + original_blocks[-1] = greeting_block_button( + str(re.match(r"\((@.*)\)", parsed_body.message.blocks[0]["text"]["text"])), + ) + modified_blocks = original_blocks + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=False, + ) + await context.respond( + text="Modified the claim to greet the new user...", + blocks=modified_blocks, + replace_original=True, + ) diff --git a/modules/handlers/mentorship_handler.py b/modules/handlers/mentorship_handler.py new file mode 100644 index 00000000..9d35dd3b --- /dev/null +++ b/modules/handlers/mentorship_handler.py @@ -0,0 +1,230 @@ +import logging # noqa: D100 +from datetime import datetime, timezone +from typing import Any + +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.airtable import ( + mentor_table, + mentorship_affiliations_table, + mentorship_requests_table, + mentorship_services_table, + mentorship_skillsets_table, +) +from modules.models.greeting_models import UserInfo +from modules.models.mentorship_models import MentorshipRequestCreate +from modules.models.slack_models.action_models import SlackActionRequestBody +from modules.models.slack_models.command_models import SlackCommandRequestBody +from modules.models.slack_models.view_models import SlackViewRequestBody +from modules.slack.blocks.mentorship_blocks import ( + mentorship_request_view, + request_claim_blocks, + request_claim_button, + request_claim_details_block, + request_claim_reset_button, + request_claim_tagged_users_block, + request_successful_block, + request_unsuccessful_block, +) +from modules.utils import get_slack_user_by_id, log_to_thread, slack_team + +logger = logging.getLogger(__name__) + + +async def handle_mentor_request( # noqa: D103 + parsed_body: SlackCommandRequestBody, + context: AsyncBoltContext, +) -> None: + logging.info("STAGE: Handling the mentor request...") + await context.ack() + response = await context.client.views_open( + trigger_id=parsed_body.trigger_id, + view=mentorship_request_view( + services=mentorship_services_table.valid_services, + skillsets=mentorship_skillsets_table.valid_skillsets, + affiliations=mentorship_affiliations_table.valid_affiliations, + ), + ) + if response["ok"]: + logger.debug("View opened successfully") + + else: + logger.warning(f"Unable to open the view, given response: {response}") # noqa: G004 + + +async def handle_mentorship_request_form_submit( # noqa: D103 + parsed_body: SlackViewRequestBody, + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Handling the mentorship request form submission...") + await context.ack() + try: + slack_user_info = await get_slack_user_by_id( + context.client, + parsed_body.user.id, + ) + mentorship_request, airtable_record = create_mentor_request_record( + parsed_body, + slack_user_info, + ) + mentors_channel_response = await context.client.chat_postMessage( + channel=slack_team.mentors_internal_channel.id, + blocks=request_claim_blocks( + mentorship_request.service, + mentorship_request.skillsets_requested, + mentorship_request.affiliation + if isinstance(mentorship_request.affiliation, str) + else mentorship_request.affiliation[0], + mentorship_request.slack_name, + ), + text="New mentorship request received...", + ) + mentorship_requests_table.update_record( + airtable_id=airtable_record["id"], + fields_to_update={"Slack Message TS": mentors_channel_response["ts"]}, + ) + await context.client.chat_postMessage( + channel=slack_team.mentors_internal_channel.id, + thread_ts=mentors_channel_response["ts"], + text="Additional details added to mentorship request...", + blocks=[request_claim_details_block(mentorship_request.additional_details)], + ) + matching_mentors = mentorship_skillsets_table.mentors_by_skillset( + mentorship_request.skillsets_requested, + ) + retrieve_mentor_slack_names = [ + mentor.slack_name for mentor in mentor_table.valid_mentors if mentor.airtable_id in matching_mentors + ] + await context.client.chat_postMessage( + channel=slack_team.mentors_internal_channel.id, + thread_ts=mentors_channel_response["ts"], + text="Tagged users for mentorship request...", + blocks=[request_claim_tagged_users_block(retrieve_mentor_slack_names)], + link_names=True, + ) + await context.client.chat_postEphemeral( + channel=parsed_body.user.id, + user=parsed_body.user.id, + text="Successfully sent mentorship request...", + blocks=[request_successful_block()], + ) + except Exception as general_exception: + logger.exception( + f"Unable to create the mentorship request record due to error: {general_exception}", # noqa: TRY401, G004 + ) + await context.client.chat_postEphemeral( + channel=parsed_body.user.id, + user=parsed_body.user.id, + text="Mentorship request was unsuccessful...", + blocks=[request_unsuccessful_block()], + ) + + +async def handle_mentorship_request_claim( + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + """Handle a mentorship request claim submission from Slack. + + :param parsed_body: The parsed body of the Slack request. + :param context: The context object for the Slack request. + """ + logger.info("STAGE: Handling mentorship request claim...") + await context.ack() + blocks = parsed_body.message.blocks + blocks[-1] = request_claim_reset_button(parsed_body.user.username) + request_record = mentorship_requests_table.return_record_by_slack_message_ts( + timestamp=str(parsed_body.message.ts), + ) + mentorship_requests_table.update_record( + airtable_id=request_record.airtable_id, + fields_to_update={ + "Claimed": "true", + "Claimed By": parsed_body.user.username, + "Claimed On": str(datetime.now(timezone.utc)), + "Reset By": "", + }, + ) + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=True, + ) + await context.respond( + text="Someone claimed the mentorship request...", + blocks=blocks, + replace_original=True, + ) + + +async def handle_mentorship_request_claim_reset( # noqa: D103 + parsed_body: SlackActionRequestBody, + context: AsyncBoltContext, +) -> None: + logger.info("STAGE: Handling mentorship request claim reset...") + await context.ack() + blocks = parsed_body.message.blocks + blocks[-1] = request_claim_button() + request_record = mentorship_requests_table.return_record_by_slack_message_ts( + timestamp=parsed_body.message.ts, + ) + mentorship_requests_table.update_record( + airtable_id=request_record.airtable_id, + fields_to_update={ + "Claimed": "false", + "Claimed By": "", + "Reset By": parsed_body.user.username, + "Reset On": str(datetime.now(timezone.utc)), + "Reset Count": int(request_record.reset_count) + 1, + }, + ) + await log_to_thread( + client=context.client, + channel_id=parsed_body.channel.id, + message_ts=parsed_body.message.ts, + username=parsed_body.user.username, + action_ts=parsed_body.actions[0].action_ts, + claim=False, + ) + await context.respond( + text="Someone reset the claimed mentorship request...", + blocks=blocks, + replace_original=True, + ) + + +def create_mentor_request_record( # noqa: D103 + parsed_body: SlackViewRequestBody, + slack_user_info: UserInfo, +) -> tuple[MentorshipRequestCreate, dict[str, Any]]: + logger.info("STAGE: Creating the mentorship request record...") + try: + mentorship_request = MentorshipRequestCreate( + slack_name=slack_user_info.name, + email=slack_user_info.email, + service=parsed_body.view.state["values"]["mentorship_service_input"]["mentorship_service_selection"][ + "selected_option" + ]["value"], + additional_details=parsed_body.view.state["values"]["details_input_block"]["details_text_input"]["value"], + skillsets_requested=[ + skill["value"] + for skill in parsed_body.view.state["values"]["mentor_skillset_input"][ + "mentorship_skillset_multi_selection" + ]["selected_options"] + ], + affiliation=parsed_body.view.state["values"]["mentorship_affiliation_input"][ + "mentorship_affiliation_selection" + ]["selected_option"]["text"]["text"], + ) + modified_request = {k.title().replace("_", " "): v for k, v in mentorship_request.__dict__.items()} + created_record = mentorship_requests_table.create_record(modified_request) + return mentorship_request, created_record # noqa: TRY300 + except Exception as exc: + logger.exception( + f"Unable to create the Airtable record for user: {slack_user_info.name} due to an exception", # noqa: G004 + exc, # noqa: TRY401 + ) + raise exc # noqa: TRY201 diff --git a/modules/handlers/report_handler.py b/modules/handlers/report_handler.py new file mode 100644 index 00000000..3ae16c6e --- /dev/null +++ b/modules/handlers/report_handler.py @@ -0,0 +1,102 @@ +import logging # noqa: D100 +from typing import Any + +from slack_bolt.context.async_context import AsyncBoltContext + +from modules.models.slack_models.shared_models import SlackUserInfo +from modules.models.slack_models.slack_models import SlackResponseBody +from modules.slack.blocks.report_blocks import ( + report_claim_blocks, + report_claim_button, + report_claim_claimed_button, + report_failed_ephemeral_message, + report_form_view_elements, + report_received_ephemeral_message, +) +from modules.utils import get_team_info, log_to_thread + +logger = logging.getLogger(__name__) + + +async def handle_report(body: dict[str, Any], context: AsyncBoltContext) -> None: # noqa: D103 + await context.ack() + await context.client.views_open( + trigger_id=body["trigger_id"], + view=report_form_view_elements(), + ) + + +async def handle_report_submit(body: dict[str, Any], context: AsyncBoltContext) -> None: # noqa: D103 + await context.ack() + slack_team = get_team_info() + logger.debug(f"Parsing received body: {body}") # noqa: G004 + parsed_body = SlackResponseBody( + **body, + originating_user=SlackUserInfo(**body["user"]), + ) + response = await context.client.chat_postMessage( + channel=slack_team.moderators_channel.id, + blocks=report_claim_blocks( + parsed_body.originating_user.username, + parsed_body.view.state["values"]["report_input"]["report_input_field"]["value"], + ), + text="New report submitted...", + ) + if response.data["ok"]: + await context.client.chat_postEphemeral( + channel=parsed_body.originating_user.id, + text="Successfully sent report to moderators...", + blocks=[report_received_ephemeral_message()], + user=parsed_body.originating_user.id, + ) + else: + await context.client.chat_postEphemeral( + channel=parsed_body.originating_user.id, + text="There was an issue sending your report...", + blocks=[report_failed_ephemeral_message()], + user=parsed_body.originating_user.id, + ) + + +async def handle_report_claim( # noqa: D103 + body: SlackResponseBody, + context: AsyncBoltContext, +) -> None: + await context.ack() + blocks = body.message.blocks + blocks[-1] = report_claim_claimed_button(body.originating_user.username) + await log_to_thread( + client=context.client, + channel_id=body.channel.id, + message_ts=body.message.ts, + username=body.originating_user.username, + action_ts=body.actions[0].action_ts, + claim=True, + ) + await context.respond( + text="Modified the claim to reach out about the report...", + blocks=blocks, + replace_original=True, + ) + + +async def handle_reset_report_claim( # noqa: D103 + body: SlackResponseBody, + context: AsyncBoltContext, +) -> None: + await context.ack() + blocks = body.message.blocks + blocks[-1] = report_claim_button() + await log_to_thread( + client=context.client, + channel_id=body.channel.id, + message_ts=body.message.ts, + username=body.originating_user.username, + action_ts=body.actions[0].action_ts, + claim=False, + ) + await context.respond( + text="Modified the claim to reach out about the report...", + blocks=blocks, + replace_original=True, + ) diff --git a/modules/models/__init__.py b/modules/models/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/models/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/models/daily_programmer_models.py b/modules/models/daily_programmer_models.py new file mode 100644 index 00000000..d60cf6d2 --- /dev/null +++ b/modules/models/daily_programmer_models.py @@ -0,0 +1,42 @@ +from datetime import datetime # noqa: D100 + +from pydantic import Field + +from modules.models.shared_models import AirtableRowBaseModel + + +class DailyProgrammerInfo(AirtableRowBaseModel): # noqa: D101 + name: str = Field( + ..., + example="Minimum Absolute Difference", + description="The display name of the daily programmer entry - will be wrapped in [] in the text from Slack", + ) + slug: str = Field( + ..., + example="minimum_absolute_difference", + description="A more parseable representation of the name of the message - should be snake cased; this is set by formula in Airtable based on the Message Name field", # noqa: E501 + ) + text: str = Field( + ..., + example="Your report has been received :check_mark:", + description="The text of the message - utilizes Slack's mrkdwn format", + ) + category: str = Field( + ..., + example="mentorship_request", + description="Snake cased category of the message", + ) + initially_posted_on: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="ISO formatted datetime in UTC for when the message was first posted to the channel", + ) + last_posted_on: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="ISO formatted datetime in UTC for when the message was last posted to the channel", + ) + posted_count: int = Field( + ..., + description="The number of time this message has been posted to the channel", + ) diff --git a/modules/models/greeting_models.py b/modules/models/greeting_models.py new file mode 100644 index 00000000..1a6ea434 --- /dev/null +++ b/modules/models/greeting_models.py @@ -0,0 +1,44 @@ +"""Models for the greeting module.""" +from pydantic import BaseModel, Field + + +class UserInfo(BaseModel): + """User info schema.""" + + id: str = Field( + ..., + example="U02RK2AL5LZ", + description="The Slack ID of the new user", + ) + name: str = Field( + ..., + example="julio123", + description="The Slack name of the new user", + ) + first_name: str = Field( + None, + example="Julio", + description="The first name of the new user", + ) + last_name: str = Field( + None, + example="Mendez", + description="The last name of the new user", + ) + display_name: str = Field( + None, + example="julio123", + description="The display name chosen by the user", + ) + real_name: str = Field( + None, + example="Julio Mendez", + description="The display name of the new user as entered by the user", + ) + email: str = Field(..., example="test@example.com", description="Email of the user") + zip_code: str = Field(None, example="12345", description="The zip code of the user") + joined_date: str = Field( + None, + example="2013-01-30", + description="The date the user joined the OC Slack", + ) diff --git a/modules/models/mentorship_models.py b/modules/models/mentorship_models.py new file mode 100644 index 00000000..74ee04b4 --- /dev/null +++ b/modules/models/mentorship_models.py @@ -0,0 +1,185 @@ +from datetime import datetime # noqa: D100 +from typing import Union + +from pydantic import BaseModel, Field + +from modules.models.shared_models import AirtableRowBaseModel + + +class MentorshipService(AirtableRowBaseModel): # noqa: D101 + name: str = Field( + ..., + example="Pair Programming", + description="Name of the service", + ) + slug: str = Field( + ..., + example="pair_programming", + description="Snake cased value for the service, used for identification and other purposes", + ) + description: str = Field( + ..., + example="Work on a programming problem with a mentor while on a call", + description="Description of the service", + ) + + +class MentorshipSkillset(AirtableRowBaseModel): # noqa: D101 + name: str = Field( + ..., + example="Pair Programming", + description="Name of the service", + ) + slug: str = Field( + ..., + example="pair_programming", + description="Snake cased value for the service, used for identification and other purposes", + ) + mentors: list[str] = Field( + None, + example="['recoakW045JkGgQB7', 'rec9Un0YIvPsFjPZh', 'recnfnbHDZdie8jcD']", + description="List of Airtable record IDs for mentors that have this skillset", + ) + + +class MentorshipAffiliation(AirtableRowBaseModel): # noqa: D101 + name: str = Field( + ..., + example="US Veteran", + description="The name of the affiliation", + ) + slug: str = Field( + ..., + example="us_veteran", + description="A more parseable slug for the affiliation, set by a formula in Airtable", + ) + description: str = Field( + ..., + example="Veterans are former members of the United States military.", + description="A short description of the affiliation", + ) + + +class Mentor(AirtableRowBaseModel): # noqa: D101 + slack_name: str = Field( + ..., + example="john123", + description="The Slack username for the mentor", + ) + full_name: str = Field( + ..., + example="John Smith", + description="The full name of the mentor", + ) + email: str = Field(..., example="test@example.com", description="Email of the user") + active: bool = Field(..., description="Whether or not the mentor is current active") + skills: list[str] = Field( + ..., + example="['recoakW045JkGgQB7', 'rec9Un0YIvPsFjPZh', 'recnfnbHDZdie8jcD']", + description="The Airtable provided IDs of the skillsets the mentor has added", + ) + desired_mentorship_hours_per_week: int = Field( + ..., + description="The number of hours the mentor has specified they would like to mentor for", + ) + time_zone: str = Field( + ..., + example="Indian/Maldives", + description="The mentor's time zone", + ) + max_mentees: int = Field( + ..., + description="The maximum number of mentees this mentor wants to work with at one time", + ) + bio: str = Field(None, description="The self provided bio for the mentor") + notes: str = Field(None, description="Any additional notes on the mentor") + mentees_worked_with: list[str] = Field( + None, + example="['recCMMhN5j51NoagK']", + description="The Airtable provided IDs of the mentees that the mentor has worked with, found on the Mentor Request table", # noqa: E501 + ) + code_of_conduct_accepted: bool = Field( + ..., + description="Whether or not the mentor has accepted the code of conduct", + ) + guidebook_read: bool = Field( + ..., + description="Whether or not the mentor has read the guidebook", + ) + row_id: int = Field(..., description="Row ID from the Airtable table") + + +class MentorshipRequestBase(BaseModel): # noqa: D101 + slack_name: str = Field( + ..., + example="john123", + description="The Slack username for the user making the mentorship request", + ) + email: str = Field( + ..., + example="test@example.com", + description="Email of the requesting user", + ) + service: str = Field( + ..., + example="Career Guidance", + description="Service requested for the mentorship session", + ) + additional_details: str = Field( + ..., + example="I need help with choosing a career path.", + description="Details provided by the user making the request", + ) + skillsets_requested: list[str] = Field( + ..., + example="['Go', 'React', 'Code Review']", + description="List of all skillsets selected by the user making the request - this is used to match a mentor", + ) + affiliation: str | list[str] = Field( + ..., + example="recCMMhN5j51NoagK", + description="The Airtable created ID of a record on the Affiliations table", + ) + claimed: bool = Field( + False, # noqa: FBT003 + description="Whether or not the mentor request has been claimed", + ) + claimed_by: Union[str, list[str]] = Field( # noqa: UP007 + None, + description="The Airtable ID of the user who has claimed the request - this is pulled from the Mentor table", + ) + claimed_on: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="ISO formatted UTC time when the request was claimed", + ) + reset_by: str = Field( + None, + example="john123", + description="Slack username of the user who reset the claim", + ) + reset_on: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="ISO formatted UTC time when the request claim was reset", + ) + reset_count: int = Field( + 0, + description="The number of times the request claim was reset", + ) + + +class MentorshipRequest(MentorshipRequestBase, AirtableRowBaseModel): # noqa: D101 + row_id: int = Field( + None, + description="The Airtable created row ID of the row, primarily used for sorting", + ) + slack_message_ts: float = Field( + ..., + example=1640727458.000000, + description="The message timestamp - this along with the channel ID allow the message to be found", + ) + + +class MentorshipRequestCreate(MentorshipRequestBase): # noqa: D101 + pass diff --git a/modules/models/message_text_models.py b/modules/models/message_text_models.py new file mode 100644 index 00000000..67e1d997 --- /dev/null +++ b/modules/models/message_text_models.py @@ -0,0 +1,33 @@ +"""Models related to message text.""" +from pydantic import Field + +from modules.models.shared_models import AirtableRowBaseModel + + +class MessageTextInfo(AirtableRowBaseModel): + """The message text info model. + + This model represents messages that are sent to different channels in Slack. + """ + + name: str = Field( + ..., + example="Report Received", + description="The display name of the message text", + ) + slug: str = Field( + ..., + example="report_received", + description="A more parseable representation of the name of the message - should be snake cased; " + "this is set by formula in Airtable based on the Message Name field", + ) + text: str = Field( + ..., + example="Your report has been received :check_mark:", + description="The text of the message - utilizes Slack's mrkdwn format", + ) + category: str = Field( + ..., + example="mentorship_request", + description="Snake cased category of the message", + ) diff --git a/modules/models/report_models.py b/modules/models/report_models.py new file mode 100644 index 00000000..5892c6a6 --- /dev/null +++ b/modules/models/report_models.py @@ -0,0 +1 @@ +"""Models related to reports.""" diff --git a/modules/models/scheduled_message_models.py b/modules/models/scheduled_message_models.py new file mode 100644 index 00000000..c8329c8c --- /dev/null +++ b/modules/models/scheduled_message_models.py @@ -0,0 +1,79 @@ +"""Models related to scheduled messages.""" +from datetime import datetime +from enum import Enum + +from pydantic import Field, field_validator +from pydantic_core.core_schema import FieldValidationInfo + +from modules.models.shared_models import AirtableRowBaseModel + + +class FrequencyEnum(str, Enum): + """Enum for message frequency.""" + + daily = "daily" + weekly = "weekly" + monthly = "monthly" + + +class ScheduledMessageInfo(AirtableRowBaseModel): + """The scheduled message info model.""" + + name: str = Field( + ..., + example="Mentorship Reminder", + description="The display name of the message to be scheduled", + ) + slug: str = Field( + ..., + example="mentorship_reminder", + description="A more parseable representation of the name of the scheduled message - should be snake cased", + ) + channel: str = Field( + ..., + example="general", + description="Channel to send the message to", + ) + message_text: str = Field( + ..., + example="Don't forget you can use the `/mentor` command to request a 1 on 1 session with a mentor!", + description="A text string that can contain markdown syntax to be posted to Slack", + ) + initial_date_time_to_send: datetime = Field( + ..., + example="2021-04-23T10:20:30.400+00:00", + description="ISO formatted datetime in UTC to send the first message - " + "this is used to set the schedule for this message", + ) + frequency: str = Field( + ..., + example="daily", + description="Frequency to send the message - one of daily, weekly, monthly", + ) + scheduled_next: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="When the message was last scheduled to send", + ) + when_to_send: datetime = Field( + ..., + example="2021-04-23T10:20:30.400+00:00", + description="When to send the message - this is calculated using a formula on the Airtable table", + ) + + @field_validator("frequency") + def frequency_must_be_valid( + cls: "ScheduledMessageInfo", # noqa: N805 + frequency: str, + info: FieldValidationInfo, # noqa: ARG002 + ) -> str: + """Validate that the passed in frequency is a valid option. + + :param frequency: The frequency to validate. + :param info: The field validation info. + :return: The frequency if it is valid. + """ + if frequency.lower() not in FrequencyEnum.__members__: + exception_message = f"Frequency must be one of {FrequencyEnum.__members__.keys()}" + raise ValueError(exception_message) + return frequency.lower() diff --git a/modules/models/shared_models.py b/modules/models/shared_models.py new file mode 100644 index 00000000..255e87f2 --- /dev/null +++ b/modules/models/shared_models.py @@ -0,0 +1,54 @@ +"""Shared models for Airtable tables.""" +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, Field + + +class ValidEnum(str, Enum): + """Enum for valid and invalid.""" + + valid = "valid" + invalid = "invalid" + + +class AirtableUser(BaseModel): + """Model for Airtable user.""" + + id: str = Field( + ..., + example="usrAuExK7DEWFNiI6", + description="Airtable provided unique ID of the user", + ) + email: str = Field(..., example="test@example.com", description="Email of the user") + name: str = Field(..., example="John Smith", description="Name of the user") + + +class AirtableRowBaseModel(BaseModel): + """Base model for Airtable rows.""" + + airtable_id: str = Field( + ..., + example="rec8CRVRJOKYBIDIL", + description="Airtable provided unique ID for the row", + ) + created_at: datetime = Field( + ..., + example="2021-04-23T10:20:30.400+00:00", + description="When the Airtable record was created", + ) + last_modified: datetime = Field( + None, + example="2021-04-23T10:20:30.400+00:00", + description="When the Airtable record was last updated", + ) + last_modified_by: AirtableUser = Field( + None, + example="JulioMendez", + description="Name of the user who last modified the Airtable record", + ) + valid: ValidEnum = Field( + None, + example="invalid", + description="Whether or not the record is valid - this is calculated on the Airtable table and has a value of valid if all fields are filled out", # noqa: E501 + ) diff --git a/modules/models/slack_models/__init__.py b/modules/models/slack_models/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/models/slack_models/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/models/slack_models/action_models.py b/modules/models/slack_models/action_models.py new file mode 100644 index 00000000..77ea9d68 --- /dev/null +++ b/modules/models/slack_models/action_models.py @@ -0,0 +1,43 @@ +from pydantic import Field # noqa: D100 + +from modules.models.slack_models.shared_models import ( + BaseSlackTeamInfo, + BasicSlackRequest, + SlackActionContainerInfo, + SlackActionInfo, + SlackChannelInfo, + SlackMessageInfo, + SlackUserInfo, +) + + +class SlackActionRequestBody(BasicSlackRequest): + """The body of a Slack action request.""" + + type: str = Field(..., example="block_actions", description="The type of action") + user: SlackUserInfo = Field( + ..., + description="The user who triggered the action request", + ) + container: SlackActionContainerInfo = Field( + ..., + description="The container where the action was triggered", + ) + team: BaseSlackTeamInfo = Field(..., description="Basic team information") + channel: SlackChannelInfo = Field( + ..., + description="The channel the action was triggered in", + ) + message: SlackMessageInfo = Field( + ..., + description="The original message where the action was triggered", + ) + response_url: str = Field( + ..., + example="https://hooks.slack.com/actions/T01SBLCQ57A/2899731511204/xb8gxI4ldtCaVwbdsddM0nb", + description="The response URL where a response can be sent if needed", + ) + actions: list[SlackActionInfo] = Field( + ..., + description="The action information about the action that was triggered", + ) diff --git a/modules/models/slack_models/command_models.py b/modules/models/slack_models/command_models.py new file mode 100644 index 00000000..63772d63 --- /dev/null +++ b/modules/models/slack_models/command_models.py @@ -0,0 +1,46 @@ +from pydantic import Field # noqa: D100 + +from modules.models.slack_models.shared_models import BasicSlackRequest + + +class SlackCommandRequestBody(BasicSlackRequest): + """The body of a Slack command request. + + These are typically received from the Slack application after a slash command is used. + """ + + command: str = Field( + ..., + example="/mentor_request", + description="The command that triggered the request", + ) + user_id: str = Field( + ..., + example="U01RN31JSTT", + description="The Slack user ID for the user who triggered the request", + ) + user_name: str = Field( + ..., + example="john123", + description="The Slack user name for the user who triggered the request", + ) + channel_id: str = Field( + ..., + example="D02R6CR6DMG", + description="The Slack channel ID where the command was triggered", + ) + channel_name: str = Field( + ..., + example="directmessage", + description="The name of the channel where the command was triggered", + ) + response_url: str | None = Field( + None, + example="https://hooks.slack.com/actions/T01SBLfdsaQ57A/2902419552385/BiWpNhRSURKF9CvqujZ3x1MQ", + description="The URL to send the response to that will automatically put the response in the right place", + ) + team_id: str = Field( + ..., + example="T01SBLCQ57A", + description="The Slack ID of the team that the command came from", + ) diff --git a/modules/models/slack_models/event_models.py b/modules/models/slack_models/event_models.py new file mode 100644 index 00000000..91f3b6b2 --- /dev/null +++ b/modules/models/slack_models/event_models.py @@ -0,0 +1,72 @@ +from pydantic import BaseModel, Field # noqa: D100 + +from modules.models.slack_models.shared_models import SlackMessageInfo + + +class MemberJoinedChannelEvent(BaseModel): + """The body of a Slack member_joined_channel event.""" + + type: str = Field( + ..., + example="member_joined_channel", + description="The type of event, should always be member_joined_channel", + ) + user: str = Field( + ..., + example="U123456789", + description="The Slack ID of the user who joined the channel", + ) + channel: str = Field( + ..., + example="C0698JE0H", + description="The Slack ID of the channel the user joined", + ) + channel_type: str = Field( + ..., + example="C", + description="The channel type - C is typically a public channel and G is for a private channel or group", + ) + team: str = Field(..., example="T024BE7LD", description="The Slack ID of the team") + inviter: str = Field( + None, + example="U123456789", + description="The Slack user ID of the user who invited the joining user - is optional and won't show up for default channels, for example", # noqa: E501 + ) + + +class MessageReceivedChannelEvent(BaseModel): + """The body of a Slack message event.""" + + team_id: str = Field( + ..., + example="T024BE7LD", + description="The Slack ID of the team", + ) + api_app_id: str = Field( + ..., + example="A02R6C6S9JN", + description="The Slack application ID", + ) + event: SlackMessageInfo = Field( + ..., + description="The information about the message that was received", + ) + type: str = Field( + ..., + example="event_callback", + description="The type of event, should always be event_callback", + ) + event_id: str = Field( + ..., + example="Ev02UJP6HDBR", + description="The Slack provided ID of the event", + ) + event_time: int = Field( + ..., + example=1642732981, + description="The Unix timestamp of the event", + ) + event_context: str = Field( + ..., + example="4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUMDFTQkxDUTU3QSIsImFpZCI6IkEwMlI2QzZTOUpOIiwiY2lkIjoiQzAxUlUxTUhNRkUifQ", + ) diff --git a/modules/models/slack_models/message_models.py b/modules/models/slack_models/message_models.py new file mode 100644 index 00000000..3719a90e --- /dev/null +++ b/modules/models/slack_models/message_models.py @@ -0,0 +1 @@ +"""Models for messages. Unused for now.""" diff --git a/modules/models/slack_models/shared_models.py b/modules/models/slack_models/shared_models.py new file mode 100644 index 00000000..d49c6ac6 --- /dev/null +++ b/modules/models/slack_models/shared_models.py @@ -0,0 +1,428 @@ +import logging # noqa: D100 +import os +from typing import Any + +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class SlackUserInfo(BaseModel): + """Information about a Slack user.""" + + id: str = Field( + ..., + example="U01RN31JSTD", + description="Slack ID of the user", + ) + username: str = Field( + ..., + example="julio123", + description="The Slack username of the user", + ) + name: str = Field( + ..., + example="JulioMendez", + description="The Slack display name of the user", + ) + + +class SlackEditedInfo(BaseModel): + """Information about when a Slack message was last edited.""" + + user: str = Field( + ..., + example="B02QRQ4KU5V", + description="The user who last edited the message", + ) + ts: str = Field( + ..., + example="1640727458.000000", + description="The Unix Epoch timestamp of when the message was last edited", + ) + + +class SlackTextObjectInfo(BaseModel): + """A Slack text object is a piece of text that can be used in a Slack message or block.""" + + type: str = Field(..., example="mrkdwn", description="The type of text object") + text: str = Field( + ..., + example="Testing text for a text object", + description="The text that makes up the text object", + ) + + +class SlackBlockInfo(BaseModel): + """A Slack block is a section of a message.""" + + type: str = Field(..., example="section", description="The type of block") + block_id: str = Field( + ..., + example="report_title_block", + description="ID of the block - must be unique within the immediate set of blocks. Will be added by Slack if it's missing in the definition", # noqa: E501 + ) + text: SlackTextObjectInfo | None = Field( + None, + description="Optional text object for this block", + ) + + class Config: + # Allows extra attributes on this model + extra = "allow" + + +class SlackViewInfo(BaseModel): + """A Slack view is a modal that can be used to collect information from a user.""" + + id: str = Field( + ..., + example="V02S65HDH9Q", + description="Slack ID of the view", + ) + type: str = Field(..., example="modal", description="The type of view") + blocks: list[SlackBlockInfo] = Field( + ..., + description="List of blocks in the view - there must be at least one", + ) + private_metadata: str | None = Field( + None, + description="Private data that can be included on a view and sent with a submission - not visible to the user", + ) + callback_id: str = Field( + ..., + example="report_form_submit", + description="Callback for the submission action of the view, used to handle the submission", + ) + state: dict[str, Any] | None = Field( + None, + description="State of a view, if it exists - contains the value of the input elements in the view", + ) + hash: str = Field( + ..., + example="1640903702.u8C2NM3Y", + description="Hash string sent with the submission of the view, this is used by the update and publish views API calls to ensure that only the most recent view is updated or published", # noqa: E501 + ) + title: SlackTextObjectInfo | None = Field( + None, + description="The text object used for the title of the view", + ) + previous_view_id: str | None = Field( + None, + example="V02S65HDH9Q", + description="The previous view's ID - typically used in workflows", + ) + root_view_id: str | None = Field( + None, + example="V02S65HDH9Q", + description="The root view's ID", + ) + external_id: str | None = Field( + None, + example="report_form_modal", + description="The optional external ID for the view, must be unique across all views - this is added by the bot", # noqa: E501 + ) + bot_id: str | None = Field( + None, + example="B02QRQ4KU5V", + description="The ID of the bot that generated the view", + ) + + +class SlackMessageInfo(BaseModel): # noqa: D101 + client_msg_id: str | None = Field(None, example="de437daf-67fd-48a6-b9bd-03f9336509e9") + bot_id: str | None = Field( + None, + example="B02QRQ4KU5V", + description="Slack ID of the bot that sent the message - provided that the original message was sent " + "from a bot", + ) + type: str = Field(..., example="message", description="The type of message") + text: str | None = Field( + None, + example="Typical fallback text...", + description="If blocks are provided, this is the fallback text for the message. If no blocks are present, " + "this is the message", + ) + user: str = Field( + ..., + example="U02RK2AL5LZ", + description="Slack user ID of the user who triggered the action", + ) + blocks: list[Any | SlackBlockInfo] | None = Field( + None, + description="The list of blocks for a particular message", + ) + ts: str = Field( + ..., + example="1640727423.003500", + description="Unix Epoch timestamp the message was received by Slack - typically used to locate the message", + ) + edited: SlackEditedInfo | None = Field( + None, + description="Information about who and when the message was last edited", + ) + thread_ts: str | None = Field( + None, + example="1640727423.003500", + description="The Unix Epoch timestamp the thread was created", + ) + reply_count: int | None = Field(None, description="The number of replies the message has") + reply_users_count: int | None = Field( + None, + description="The number of users who have replied to the message", + ) + latest_reply: str | None = Field( + None, + example="1640727423.003500", + description="The Unix Epoch timestamp of when the latest reply was created", + ) + reply_users: list[str] | None = Field( + None, + example=["U02RK2AL5LZ"], + description="A list of Slack user IDs of users who have replied to the message", + ) + last_read: str | None = Field( + None, + example="1640727423.003500", + description="The Unix Epoch timestamp of when the message was last read", + ) + + class Config: + arbitrary_types_allowed = True + + +class SlackActionInfo(BaseModel): + """The action information for a Slack action.""" + + action_id: str = Field( + ..., + example="reset_greet_new_user_claim", + description="The ID that identifies this particular action and allows the application to" + " handle it when triggered", + ) + block_id: str | None = Field( + None, + example="reset_claim_action", + description="The ID that identifies the block the action is part of", + ) + text: SlackTextObjectInfo | None = Field( + None, + description="The text object that represents the text on the action (button, etc)", + ) + value: dict[str, Any] | str | None = Field( + None, + description="The value sent to the application when the action is triggered", + ) + style: str | None = Field( + None, + example="danger", + description="The style of the action, typically the style of the button", + ) + type: str = Field(..., example="button", description="The type of action") + action_ts: str = Field( + ..., + example="1640727423.003500", + description="The Unix Epoch timestamp of when the action was triggered", + ) + + +class SlackActionContainerInfo(BaseModel): + """The container information for a Slack action.""" + + type: str = Field( + ..., + example="message", + description="The type of container the action came from", + ) + message_ts: str = Field( + ..., + example="1640752131.000200", + description="Unix Epoch timestamp of when the message was sent to Slack, typically used to locate the message", + ) + channel_id: str = Field( + ..., + example="C01S0K034TB", + description="The channel ID the message came from", + ) + is_ephemeral: bool = Field( + ..., + description="Whether or not the message is ephemeral", + ) + + +class SlackChannelInfo(BaseModel): + """Information about a Slack channel.""" + + id: str = Field(..., example="C01S0K034TB", description="Slack ID of the channel") + name: str = Field(..., example="general", description="Name of the channel") + + +class BasicSlackRequest(BaseModel): + """The basic information that is included in all Slack requests.""" + + trigger_id: str = Field( + ..., + example="2875577934983.1895692821248.5b6bb2ed4127b90954e8d32a86e2cafc", + description="The ID of the trigger for this request, typically used to respond to the correct place and user", + ) + api_app_id: str = Field( + ..., + example="A02R6C6S9JN", + description="The Slack application ID", + ) + + +class SlackConversationInfo(BaseModel): + """Slack used to call these channels, but now they are called conversations. + + Of which channels are a subset along with IMs and MPIMs (Multi Person IMs). + """ + + id: str = Field( + ..., + example="C012AB3CD", + description="Slack ID of the conversation", + ) + name: str = Field(..., example="general", description="Name of the conversation") + is_channel: bool = Field( + ..., + description="Whether the conversation is a channel or not", + ) + is_im: bool = Field(..., description="Whether the conversation is an IM or not") + is_mpim: bool = Field( + ..., + description="Whether the conversation is a Multi Person IM or not", + ) + is_private: bool = Field( + ..., + description="Whether the conversation is private or not", + ) + + +class BaseSlackTeamInfo(BaseModel): + """Basic information about a Slack team.""" + + id: str = Field( + ..., + example="T01SBLCQ57A", + description="Slack ID of the team", + ) + domain: str | None = Field( + None, + example="bot-testing-field", + description="The domain of the team", + ) + + +class SlackTeamInfo(BaseSlackTeamInfo): + """Slack used to call these workspaces, but they are referred to as teams now.""" + + name: str = Field( + ..., + example="Bot-Testing-Field", + description="The name of the Slack workspace", + ) + conversations: list[SlackConversationInfo] = Field( + ..., + description="The list of Slack channels in this workspace", + ) + + +class SlackTeam: + """A Slack team is a workspace that contains channels and users.""" + + def __init__(self: "SlackTeam", team_info: SlackTeamInfo) -> None: + """Initialize the Slack team with the team information.""" + logger.info("Initializing the Slack Team with team_info", extra={"team_info": team_info}) + self._team_info = team_info + + def find_channel_by_name(self: "SlackTeam", channel_name: str) -> SlackConversationInfo: + """Find a Slack channel by name. + + :param channel_name: The name of the channel to find + :return: The Slack channel information + """ + logger.info("Finding channel by name", extra={"channel_name": channel_name}) + full_channel_list = [conversation_info.name for conversation_info in self.full_conversation_list] + logger.info("Full channel list:", extra={"full_channel_list": full_channel_list}) + try: + return next( + conversation for conversation in self.full_conversation_list if conversation.name == channel_name + ) + + except IndexError: + logger.exception("Could not find channel by name", extra={"channel_name": channel_name}) + raise + + @property + def slack_id(self: "SlackTeam") -> str: + """Return the Slack ID of the team. + + :return: The Slack ID of the team + """ + return self._team_info.id + + @property + def name(self: "SlackTeam") -> str: + """Return the name of the team. + + :return: The name of the team + """ + return self._team_info.name + + @property + def full_conversation_list(self: "SlackTeam") -> list[SlackConversationInfo]: + """Return the full list of conversations in the team. + + :return: The full list of conversations in the team + """ + return self._team_info.conversations + + @property + def greetings_channel(self: "SlackTeam") -> SlackConversationInfo: + """Return the greeting channel. + + :return: The greeting channel + """ + return self.find_channel_by_name(os.getenv("GREETINGS_CHANNEL_NAME", "")) + + @property + def mentors_internal_channel(self: "SlackTeam") -> SlackConversationInfo: + """Return the mentor internal channel. + + :return: The mentor internal channel + """ + return self.find_channel_by_name(os.getenv("MENTORS_CHANNEL_NAME", "")) + + @property + def moderators_channel(self: "SlackTeam") -> SlackConversationInfo: + """Return the moderator channel. + + :return: The moderator channel + """ + return self.find_channel_by_name(os.getenv("MODERATORS_CHANNEL_NAME", "")) + + @property + def general_channel(self: "SlackTeam") -> SlackConversationInfo: + """Return the general channel. + + :return: The general channel + """ + return self.find_channel_by_name(os.getenv("GENERAL_CHANNEL_NAME", "")) + + @property + def pride_channel(self: "SlackTeam") -> SlackConversationInfo: + """Return the pride channel. + + :return: The pride channel + """ + return self.find_channel_by_name(os.getenv("PRIDE_CHANNEL_NAME", "")) + + @property + def blacks_in_tech(self: "SlackTeam") -> SlackConversationInfo: + """Return the blacks_in_tech channel. + + :return: The blacks_in_tech channel + """ + return self.find_channel_by_name(os.getenv("BLACKS_IN_TECH_CHANNEL_NAME", "")) diff --git a/modules/models/slack_models/slack_models.py b/modules/models/slack_models/slack_models.py new file mode 100644 index 00000000..e31ed987 --- /dev/null +++ b/modules/models/slack_models/slack_models.py @@ -0,0 +1,119 @@ +from typing import Any # noqa: D100 + +from pydantic import BaseModel, Field + +from modules.models.slack_models.shared_models import ( + BasicSlackRequest, + SlackActionContainerInfo, + SlackActionInfo, + SlackChannelInfo, + SlackMessageInfo, + SlackUserInfo, + SlackViewInfo, +) + + +class SlackResponseBody(BasicSlackRequest): + """The body of a Slack response.""" + + type: str = Field( + ..., + example="view_submission", + description="The type of request the response is responding to", + ) + originating_user: SlackUserInfo = Field( + ..., + description="The info of the user who triggered the request", + ) + view: SlackViewInfo | None = Field( + None, + description="View object of the original message if it exists", + ) + container: SlackActionContainerInfo | None = Field( + None, + description="The container that the action originated from if it exists", + ) + channel: SlackChannelInfo | None = Field( + None, + description="The channel information for where the original request was from", + ) + message: SlackMessageInfo | None = Field( + None, + description="The original message from the request, if it exists", + ) + response_urls: list[str] | None = Field( + None, + description="List of response URLs, typically included with a view response", + ) + actions: list[SlackActionInfo] | None = Field( + None, + description="The list of actions in this message", + ) + + +class BotInfo(BaseModel): + """Information about the bot that sent the request.""" + + slack_id: str = Field( + ..., + example="B02QRQ4KU5V", + description="Slack ID for the bot that sent the request", + ) + app_id: str = Field( + ..., + example="A02R6C6S9JN", + description="Slack ID for the parent application", + ) + name: str = Field( + ..., + example="retrieval-bot", + description="Name of the bot that sent the request", + ) + team_id: str = Field( + ..., + example="T01SBLCQ57A", + description="Slack team ID of the bot that sent the request", + ) + + +class BasicSlackBotResponse(BaseModel): + """Basic information about a Slack bot response.""" + + date_time_received: str = Field( + ..., + example="Tue, 28 Dec 2021 05:36:22 GMT", + description="Timestamp for when the response was received", + ) + oauth_scopes: str = Field( + ..., + example="app_mentions:read,channels:history,channels:read,channels:join,emoji:read", + description="List of oauth scopes the bot is authorized to use", + ) + status_ok: bool = Field( + ..., + description="Status of the request that triggered the response, true means the request was successful while " + "false means it was in error", + ) + received_timestamp: str = Field( + ..., + example="1640669783.000100", + description="Unix epoch timestamp for when the request was received", + ) + + +class SlackBotResponseContent(BasicSlackBotResponse): + """The content of a Slack bot response.""" + + channel: str = Field( + ..., + example="D02R6CR6DMG", + description="Channel the request was sent to", + ) + bot_info: BotInfo = Field( + ..., + description="Information about the bot that sent the request", + ) + request_blocks: list[dict[str, Any]] | None = Field( + None, + description="List of blocks in the original request", + ) diff --git a/modules/models/slack_models/view_models.py b/modules/models/slack_models/view_models.py new file mode 100644 index 00000000..d1b58543 --- /dev/null +++ b/modules/models/slack_models/view_models.py @@ -0,0 +1,23 @@ +from pydantic import Field # noqa: D100 + +from modules.models.slack_models.shared_models import ( + BasicSlackRequest, + SlackUserInfo, + SlackViewInfo, +) + + +class SlackViewRequestBody(BasicSlackRequest): # noqa: D101 + user: SlackUserInfo = Field( + ..., + description="The Slack user object of the user who triggered the submission of the view", + ) + view: SlackViewInfo = Field( + ..., + description="The information of the view that was submitted", + ) + response_urls: list[str] = Field( + [], + example="['https://hooks.slack.com/actions/T01SBLfdsaQ57A/2902419552385/BiWpNhRSURKF9CvqujZ3x1MQ']", + description="List of URLs to be used for responses depending on if the view has elements that are configured to generate a response URL", # noqa: E501 + ) diff --git a/modules/slack/__init__.py b/modules/slack/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/slack/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/slack/blocks/__init__.py b/modules/slack/blocks/__init__.py new file mode 100644 index 00000000..6e031999 --- /dev/null +++ b/modules/slack/blocks/__init__.py @@ -0,0 +1 @@ +# noqa: D104 diff --git a/modules/slack/blocks/announcement_blocks.py b/modules/slack/blocks/announcement_blocks.py new file mode 100644 index 00000000..fcc73068 --- /dev/null +++ b/modules/slack/blocks/announcement_blocks.py @@ -0,0 +1,36 @@ +"""Slack blocks for the announcements in various channels.""" +from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTextObject +from slack_sdk.models.blocks.blocks import HeaderBlock, SectionBlock + + +def general_announcement_blocks( + header_text: str, + text: str, +) -> list[HeaderBlock | SectionBlock]: + """The blocks used for a general announcement. + + :param header_text: The text for the header. + :param text: The text for the body. + :return: A list of Header and Section blocks. + """ # noqa: D401 + return [general_announcement_header(header_text), general_announcement_body(text)] + + +def general_announcement_header(header_text: str) -> HeaderBlock: + """The header block for a general announcement. + + :param header_text: The text for the header. + :return: The header block. + """ # noqa: D401 + text = PlainTextObject(text="[" + header_text + "]", emoji=True) + return HeaderBlock(block_id="general_announcement_header", text=text) + + +def general_announcement_body(text: str) -> SectionBlock: + """The body block for a general announcement. + + :param text: The text for the body. + :return: The body block. + """ # noqa: D401 + text = MarkdownTextObject(text=text) + return SectionBlock(text=text, block_id="general_announcement_body") diff --git a/modules/slack/blocks/block_kit_examples/channel_join_request_blocks.json b/modules/slack/blocks/block_kit_examples/channel_join_request_blocks.json new file mode 100644 index 00000000..31c16f8d --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/channel_join_request_blocks.json @@ -0,0 +1,29 @@ +{ + "blocks": [ + { + "type": "section", + "block_id": "request_main", + "text": { + "type": "mrkdwn", + "text": ":tada: has requested to join the channel." + } + }, + { + "type": "actions", + "block_id": "invite_to_channel_claim", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "I'll Invite Them!", + "emoji": true + }, + "style": "primary", + "value": "juilio.mendez", + "action_id": "invite_to_channel_click" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/general_announcement.json b/modules/slack/blocks/block_kit_examples/general_announcement.json new file mode 100644 index 00000000..6d6ac68a --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/general_announcement.json @@ -0,0 +1,21 @@ +{ + "blocks": [ + { + "type": "header", + "block_id": "general_announcement_header", + "text": { + "type": "plain_text", + "text": "[Scholarship Opportunity]", + "emoji": true + } + }, + { + "type": "section", + "block_id": "general_announcement_body", + "text": { + "type": "mrkdwn", + "text": "*Coursera:* Prepare for in-demand jobs in Data Analytics, IT Support, Project Management, and UX design. ends on December 31st 2022." + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/greeting_block.json b/modules/slack/blocks/block_kit_examples/greeting_block.json new file mode 100644 index 00000000..1e2b2deb --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/greeting_block.json @@ -0,0 +1,55 @@ +{ + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":tada: has joined our community! :tada:" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*First Name:*" + }, + { + "type": "mrkdwn", + "text": "Julio" + }, + { + "type": "mrkdwn", + "text": "*Last Name:*" + }, + { + "type": "mrkdwn", + "text": "Mendez" + }, + { + "type": "mrkdwn", + "text": "*When:*" + }, + { + "type": "mrkdwn", + "text": "August 10th, 2021" + } + ] + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "emoji": true, + "text": "I will greet them!" + }, + "style": "primary", + "value": "greet_user" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/mentorship/mentorship_claim_blocks.json b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_claim_blocks.json new file mode 100644 index 00000000..814bcf51 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_claim_blocks.json @@ -0,0 +1,45 @@ +{ + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_service_text", + "text": { + "type": "mrkdwn", + "text": "User has requested a mentor for Code Review." + } + }, + { + "type": "section", + "block_id": "mentorship_request_skillset_text", + "text": { + "type": "mrkdwn", + "text": "*Requested Skillset(s):* SQL, C / C++" + } + }, + { + "type": "section", + "block_id": "mentorship_request_affiliation_text", + "text": { + "type": "mrkdwn", + "text": "*Requestor Affiliation:* US Military Veteran" + } + }, + { + "type": "actions", + "block_id": "claim_button_action_block", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Claim Mentorship Request", + "emoji": true + }, + "style": "primary", + "value": "JulioMendez", + "action_id": "claim_mentorship_request" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_block.json b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_block.json new file mode 100644 index 00000000..a2b243e5 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_block.json @@ -0,0 +1,588 @@ + +{ + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_title", + "text": { + "type": "mrkdwn", + "text": ":male-teacher: Mentor Request Form :female-teacher:\n Thank you for signing up for a mentoring session. Please fill out the form below. If you have any questions, please don't hesitate to contact @mentor-coordinators.", + "verbatim": false + } + }, + { + "type": "divider", + "block_id": "mentorship_divider_1" + }, + { + "type": "section", + "block_id": "mentor_service", + "text": { + "type": "mrkdwn", + "text": "*Type of Mentorship Service*", + "verbatim": false + }, + "accessory": { + "type": "static_select", + "action_id": "mentor_service_select", + "placeholder": { + "type": "plain_text", + "text": "Type of Service", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Career Guidance", + "emoji": true + }, + "value": "career_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Review", + "emoji": true + }, + "value": "resume_review" + }, + { + "text": { + "type": "plain_text", + "text": "General Guidance", + "emoji": true + }, + "value": "general_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + } + ] + } + }, + { + "type": "section", + "block_id": "mentor_skillset", + "text": { + "type": "mrkdwn", + "text": "*Desired Mentor Skillsets*", + "verbatim": false + }, + "accessory": { + "type": "multi_static_select", + "action_id": "mentor_skillset_select", + "placeholder": { + "type": "plain_text", + "text": "Desired Mentor Skillsets", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Job Search", + "emoji": true + }, + "value": "job_search" + }, + { + "text": { + "type": "plain_text", + "text": "Cybersecurity", + "emoji": true + }, + "value": "cybersecurity" + }, + { + "text": { + "type": "plain_text", + "text": "Functional Programming", + "emoji": true + }, + "value": "functional_programming" + }, + { + "text": { + "type": "plain_text", + "text": "General Code Review", + "emoji": true + }, + "value": "general_code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Design / UX", + "emoji": true + }, + "value": "design_ux" + }, + { + "text": { + "type": "plain_text", + "text": "Go", + "emoji": true + }, + "value": "go" + }, + { + "text": { + "type": "plain_text", + "text": "Project Management", + "emoji": true + }, + "value": "project_management" + }, + { + "text": { + "type": "plain_text", + "text": "Scala", + "emoji": true + }, + "value": "scala" + }, + { + "text": { + "type": "plain_text", + "text": "Entrepreneurship", + "emoji": true + }, + "value": "entrepreneurship" + }, + { + "text": { + "type": "plain_text", + "text": "Education Help", + "emoji": true + }, + "value": "education_help" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / GCP / Azure", + "emoji": true + }, + "value": "cloud_aws_gcp_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Career Advice", + "emoji": true + }, + "value": "career_advice" + }, + { + "text": { + "type": "plain_text", + "text": "Bot Programming", + "emoji": true + }, + "value": "bot_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Study Help", + "emoji": true + }, + "value": "study_help" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Prolog", + "emoji": true + }, + "value": "prolog" + }, + { + "text": { + "type": "plain_text", + "text": "VA Benefits / Financial Aid", + "emoji": true + }, + "value": "va_benefits_financial_aid" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (Android)", + "emoji": true + }, + "value": "mobile_development_android" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Reviews", + "emoji": true + }, + "value": "resume_reviews" + }, + { + "text": { + "type": "plain_text", + "text": "React / Angular / Vue", + "emoji": true + }, + "value": "react_angular_vue" + }, + { + "text": { + "type": "plain_text", + "text": "JavaScript", + "emoji": true + }, + "value": "javascript" + }, + { + "text": { + "type": "plain_text", + "text": "Xamarin", + "emoji": true + }, + "value": "xamarin" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Back-end)", + "emoji": true + }, + "value": "web_development_backend" + }, + { + "text": { + "type": "plain_text", + "text": "Data Science", + "emoji": true + }, + "value": "data_science" + }, + { + "text": { + "type": "plain_text", + "text": "General Architecture", + "emoji": true + }, + "value": "general_architecture" + }, + { + "text": { + "type": "plain_text", + "text": "DevOps", + "emoji": true + }, + "value": "devops" + }, + { + "text": { + "type": "plain_text", + "text": "Rust", + "emoji": true + }, + "value": "rust" + }, + { + "text": { + "type": "plain_text", + "text": "Ruby / Rails", + "emoji": true + }, + "value": "ruby_rails" + }, + { + "text": { + "type": "plain_text", + "text": "Salesforce", + "emoji": true + }, + "value": "salesforce" + }, + { + "text": { + "type": "plain_text", + "text": "Transition Assistance", + "emoji": true + }, + "value": "transition_assistance" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (iOS)", + "emoji": true + }, + "value": "mobile_development_ios" + }, + { + "text": { + "type": "plain_text", + "text": "PHP", + "emoji": true + }, + "value": "php" + }, + { + "text": { + "type": "plain_text", + "text": "Linux", + "emoji": true + }, + "value": "linux" + }, + { + "text": { + "type": "plain_text", + "text": "C# / .NET", + "emoji": true + }, + "value": "c#_.net" + }, + { + "text": { + "type": "plain_text", + "text": "Cake", + "emoji": true + }, + "value": "cake" + }, + { + "text": { + "type": "plain_text", + "text": "Python", + "emoji": true + }, + "value": "python" + }, + { + "text": { + "type": "plain_text", + "text": "Java", + "emoji": true + }, + "value": "java" + }, + { + "text": { + "type": "plain_text", + "text": "Docker / Containers", + "emoji": true + }, + "value": "docker_containers" + }, + { + "text": { + "type": "plain_text", + "text": "C / C++", + "emoji": true + }, + "value": "c_c++" + }, + { + "text": { + "type": "plain_text", + "text": "Mock Interview", + "emoji": true + }, + "value": "mock_interview" + }, + { + "text": { + "type": "plain_text", + "text": "SQL", + "emoji": true + }, + "value": "sql" + }, + { + "text": { + "type": "plain_text", + "text": "Product Management", + "emoji": true + }, + "value": "product_management" + }, + { + "text": { + "type": "plain_text", + "text": "Networking", + "emoji": true + }, + "value": "networking" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Front-end)", + "emoji": true + }, + "value": "web_development_frontend" + }, + { + "text": { + "type": "plain_text", + "text": "Introductory Coding", + "emoji": true + }, + "value": "introductory_coding" + }, + { + "text": { + "type": "plain_text", + "text": "Remote Working", + "emoji": true + }, + "value": "remote_working" + }, + { + "text": { + "type": "plain_text", + "text": "Open Source Contributing", + "emoji": true + }, + "value": "open_source_contributing" + } + ] + } + }, + { + "type": "section", + "block_id": "comments", + "text": { + "type": "mrkdwn", + "text": "*Add details* (required)", + "verbatim": false + }, + "accessory": { + "type": "button", + "action_id": "details_btn", + "text": { + "type": "plain_text", + "text": "Add details", + "emoji": true + }, + "value": "add_details" + }, + "fields": [ + { + "type": "plain_text", + "text": " ", + "emoji": true + } + ] + }, + { + "type": "divider", + "block_id": "jeZsB" + }, + { + "type": "section", + "block_id": "affiliation", + "text": { + "type": "mrkdwn", + "text": "*I certify that I am a member of the following group*", + "verbatim": false + }, + "accessory": { + "type": "static_select", + "action_id": "affiliation_select", + "placeholder": { + "type": "plain_text", + "text": "Military affiliation", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Veteran", + "emoji": true + }, + "value": "Veteran" + }, + { + "text": { + "type": "plain_text", + "text": "Active Duty", + "emoji": true + }, + "value": "Active Duty" + }, + { + "text": { + "type": "plain_text", + "text": "Military Spouse", + "emoji": true + }, + "value": "Military Spouse" + }, + { + "text": { + "type": "plain_text", + "text": "Non Veteran", + "emoji": true + }, + "value": "Non Veteran" + } + ] + } + }, + { + "type": "divider", + "block_id": "2+AO" + }, + { + "type": "actions", + "block_id": "submission", + "elements": [ + { + "type": "button", + "action_id": "submit_mentor_btn", + "text": { + "type": "plain_text", + "text": "Submit", + "emoji": true + }, + "style": "primary", + "value": "submit" + }, + { + "type": "button", + "action_id": "cancel_btn", + "text": { + "type": "plain_text", + "text": "Cancel", + "emoji": true + }, + "style": "danger", + "value": "cancel" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_modal.json b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_modal.json new file mode 100644 index 00000000..25443957 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/mentorship/mentorship_request_modal.json @@ -0,0 +1,573 @@ +{ + "type": "modal", + "title": { + "type": "plain_text", + "text": "OC Mentor Request", + "emoji": true + }, + "submit": { + "type": "plain_text", + "text": "Submit Request", + "emoji": true + }, + "close": { + "type": "plain_text", + "text": "Cancel", + "emoji": true + }, + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_main_text", + "text": { + "type": "mrkdwn", + "text": ":male-teacher: Mentor Request Form :female-teacher:\n Thank you for signing up for a mentoring session. Please fill out the form below. If you have any questions, please don't hesitate to contact @mentor-coordinators. *NOTE*: Mentors are typically available within a couple hours; however, on weekends or holidays the timeframe may be longer before someone reaches out.", + "verbatim": false + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_1" + }, + { + "type": "input", + "block_id": "mentorship_service_input", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Type of service...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Career Guidance", + "emoji": true + }, + "value": "career_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "General Guidance", + "emoji": true + }, + "value": "general_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Review", + "emoji": true + }, + "value": "resume_review" + } + ], + "action_id": "mentorship_service_selection" + }, + "label": { + "type": "plain_text", + "text": "Mentorship Service*", + "emoji": true + } + }, + { + "type": "input", + "block_id": "mentorship_skillset_input", + "element": { + "type": "multi_static_select", + "placeholder": { + "type": "plain_text", + "text": "Skills related to your request...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Job Search", + "emoji": true + }, + "value": "job_search" + }, + { + "text": { + "type": "plain_text", + "text": "Cybersecurity", + "emoji": true + }, + "value": "cybersecurity" + }, + { + "text": { + "type": "plain_text", + "text": "Functional Programming", + "emoji": true + }, + "value": "functional_programming" + }, + { + "text": { + "type": "plain_text", + "text": "General Code Review", + "emoji": true + }, + "value": "general_code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Design / UX", + "emoji": true + }, + "value": "design_ux" + }, + { + "text": { + "type": "plain_text", + "text": "Go", + "emoji": true + }, + "value": "go" + }, + { + "text": { + "type": "plain_text", + "text": "Project Management", + "emoji": true + }, + "value": "project_management" + }, + { + "text": { + "type": "plain_text", + "text": "Scala", + "emoji": true + }, + "value": "scala" + }, + { + "text": { + "type": "plain_text", + "text": "Entrepreneurship", + "emoji": true + }, + "value": "entrepreneurship" + }, + { + "text": { + "type": "plain_text", + "text": "Education Help", + "emoji": true + }, + "value": "education_help" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / GCP / Azure", + "emoji": true + }, + "value": "cloud_aws_gcp_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Career Advice", + "emoji": true + }, + "value": "career_advice" + }, + { + "text": { + "type": "plain_text", + "text": "Bot Programming", + "emoji": true + }, + "value": "bot_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Study Help", + "emoji": true + }, + "value": "study_help" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Prolog", + "emoji": true + }, + "value": "prolog" + }, + { + "text": { + "type": "plain_text", + "text": "VA Benefits / Financial Aid", + "emoji": true + }, + "value": "va_benefits_financial_aid" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (Android)", + "emoji": true + }, + "value": "mobile_development_android" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Reviews", + "emoji": true + }, + "value": "resume_reviews" + }, + { + "text": { + "type": "plain_text", + "text": "React / Angular / Vue", + "emoji": true + }, + "value": "react_angular_vue" + }, + { + "text": { + "type": "plain_text", + "text": "JavaScript", + "emoji": true + }, + "value": "javascript" + }, + { + "text": { + "type": "plain_text", + "text": "Xamarin", + "emoji": true + }, + "value": "xamarin" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Back-end)", + "emoji": true + }, + "value": "web_development_backend" + }, + { + "text": { + "type": "plain_text", + "text": "Data Science", + "emoji": true + }, + "value": "data_science" + }, + { + "text": { + "type": "plain_text", + "text": "General Architecture", + "emoji": true + }, + "value": "general_architecture" + }, + { + "text": { + "type": "plain_text", + "text": "DevOps", + "emoji": true + }, + "value": "devops" + }, + { + "text": { + "type": "plain_text", + "text": "Rust", + "emoji": true + }, + "value": "rust" + }, + { + "text": { + "type": "plain_text", + "text": "Ruby / Rails", + "emoji": true + }, + "value": "ruby_rails" + }, + { + "text": { + "type": "plain_text", + "text": "Salesforce", + "emoji": true + }, + "value": "salesforce" + }, + { + "text": { + "type": "plain_text", + "text": "Transition Assistance", + "emoji": true + }, + "value": "transition_assistance" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (iOS)", + "emoji": true + }, + "value": "mobile_development_ios" + }, + { + "text": { + "type": "plain_text", + "text": "PHP", + "emoji": true + }, + "value": "php" + }, + { + "text": { + "type": "plain_text", + "text": "Linux", + "emoji": true + }, + "value": "linux" + }, + { + "text": { + "type": "plain_text", + "text": "C# / .NET", + "emoji": true + }, + "value": "c#_.net" + }, + { + "text": { + "type": "plain_text", + "text": "Cake", + "emoji": true + }, + "value": "cake" + }, + { + "text": { + "type": "plain_text", + "text": "Python", + "emoji": true + }, + "value": "python" + }, + { + "text": { + "type": "plain_text", + "text": "Java", + "emoji": true + }, + "value": "java" + }, + { + "text": { + "type": "plain_text", + "text": "Docker / Containers", + "emoji": true + }, + "value": "docker_containers" + }, + { + "text": { + "type": "plain_text", + "text": "C / C++", + "emoji": true + }, + "value": "c_c++" + }, + { + "text": { + "type": "plain_text", + "text": "Mock Interview", + "emoji": true + }, + "value": "mock_interview" + }, + { + "text": { + "type": "plain_text", + "text": "SQL", + "emoji": true + }, + "value": "sql" + }, + { + "text": { + "type": "plain_text", + "text": "Product Management", + "emoji": true + }, + "value": "product_management" + }, + { + "text": { + "type": "plain_text", + "text": "Networking", + "emoji": true + }, + "value": "networking" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Front-end)", + "emoji": true + }, + "value": "web_development_frontend" + }, + { + "text": { + "type": "plain_text", + "text": "Introductory Coding", + "emoji": true + }, + "value": "introductory_coding" + }, + { + "text": { + "type": "plain_text", + "text": "Remote Working", + "emoji": true + }, + "value": "remote_working" + }, + { + "text": { + "type": "plain_text", + "text": "Open Source Contributing", + "emoji": true + }, + "value": "open_source_contributing" + } + ], + "action_id": "mentorship_skillset_multi_selection" + }, + "label": { + "type": "plain_text", + "text": "Skillsets Related to Your Request*", + "emoji": true + } + }, + { + "type": "input", + "block_id": "details_input_field", + "element": { + "type": "plain_text_input", + "multiline": true, + "action_id": "details_text_input", + "min_length": 10, + "placeholder": { + "type": "plain_text", + "text": "Please enter more information about your request. The more specific you are the easier it is to match you with a great mentor!", + "emoji": true + } + }, + "label": { + "type": "plain_text", + "text": "Request Details*", + "emoji": true + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_2" + }, + { + "type": "input", + "block_id": "mentorship_affiliation_input", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Please select a group...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Veteran", + "emoji": true + }, + "value": "us_veteran" + }, + { + "text": { + "type": "plain_text", + "text": "Active Duty", + "emoji": true + }, + "value": "us_active_duty" + }, + { + "text": { + "type": "plain_text", + "text": "Military Spouse", + "emoji": true + }, + "value": "us_military_spouse" + }, + { + "text": { + "type": "plain_text", + "text": "Non Veteran", + "emoji": true + }, + "value": "non_veteran" + }, + { + "text": { + "type": "plain_text", + "text": "Foreign Military", + "emoji": true + }, + "value": "foreign_military" + } + ], + "action_id": "affiliation_selection" + }, + "label": { + "type": "plain_text", + "text": "I certify that I am affiliated with one of the following groups:*", + "emoji": true + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/new_join_delayed.json b/modules/slack/blocks/block_kit_examples/new_join_delayed.json new file mode 100644 index 00000000..a8c5455b --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/new_join_delayed.json @@ -0,0 +1,20 @@ +{ + "blocks": [ + { + "type": "section", + "block_id": "delayed_welcome_first_text", + "text": { + "type": "mrkdwn", + "text": "Again, welcome to Operation Code's Slack Community, we're very glad you are here! Please share with us in #general what brings you to Operation Code if you haven't already. Also please let us know how we can assist you on your journey. Consider adding links to your LinkedIn and Github profiles on your Operation Code profile. Lastly, consider connecting with us on our , , , and . If you'd like to contribute to our Open Source software, you can do so on ." + } + }, + { + "type": "section", + "block_id": "delayed_welcome_second_text", + "text": { + "type": "mrkdwn", + "text": "We're excited to have you! If you have any immediate needs, please tag @outreach-team in any public channel." + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/new_join_immediate.json b/modules/slack/blocks/block_kit_examples/new_join_immediate.json new file mode 100644 index 00000000..25bc4826 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/new_join_immediate.json @@ -0,0 +1,93 @@ +{ + "blocks": [ + { + "type": "section", + "block_id": "immediate_welcome_main_text", + "text": { + "type": "mrkdwn", + "text": "Hello ! Welcome to Operation Code! I'm a bot designed to help you navigate this Slack workspace. Our goal here at Operation Code is to get veterans and their families started on the path to a career in tech. We do that through providing you with scholarships, mentoring, career development opportunities, conference tickets, and more! You can check out more information about us ." + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Much of the provided aid requires veteran or military spouse status. Please verify your status on your profile at https://operationcode.org if you haven't already." + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "You are currently in Slack, a chat application that serves as the hub of Operation Code. If you are visiting us via your browser, Slack provides a to make staying in touch even more convenient." + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "All active Operation Code open source projects are located on our . Lastly, please take a moment to review our ." + } + }, + { + "type": "section", + "block_id": "oc_homepage_button", + "text": { + "type": "mrkdwn", + "text": "Operation Code Homepage" + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "OC Homepage", + "emoji": true + }, + "value": "oc_home_page", + "url": "https://operationcode.org", + "action_id": "oc_greeting_homepage_click", + "style": "primary" + } + }, + { + "type": "section", + "block_id": "slack_download_button", + "text": { + "type": "mrkdwn", + "text": "Slack Download" + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Slack Download", + "emoji": true + }, + "value": "slack_download", + "url": "https://slack.com/downloads/", + "style": "primary", + "action_id": "oc_greeting_slack_download_click" + } + }, + { + "type": "section", + "block_id": "oc_coc_button", + "text": { + "type": "mrkdwn", + "text": "Operation Code CoC" + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Operation Code CoC", + "emoji": true + }, + "value": "operation_code_coc", + "url": "https://github.com/OperationCode/community/blob/master/code_of_conduct.md", + "style": "primary", + "action_id": "oc_greeting_coc_click" + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/reports/report_claim.json b/modules/slack/blocks/block_kit_examples/reports/report_claim.json new file mode 100644 index 00000000..258a741d --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/reports/report_claim.json @@ -0,0 +1,46 @@ +{ + "blocks": [ + { + "type": "section", + "block_id": "report_claim_title", + "text": { + "type": "mrkdwn", + "text": ":warning: has submitted a report. :warning:" + } + }, + { + "type": "header", + "block_id": "report_claim_header", + "text": { + "type": "plain_text", + "text": "Report details:", + "emoji": true + } + }, + { + "type": "section", + "block_id": "report_claim_details", + "text": { + "type": "mrkdwn", + "text": "I have an issue with the post made by x: blah blah blah" + } + }, + { + "type": "actions", + "block_id": "report_claim_button", + "elements": [ + { + "type": "button", + "action_id": "report_claim_button_click", + "text": { + "type": "plain_text", + "emoji": true, + "text": "I Will Reach Out to Them" + }, + "style": "primary", + "value": "claim_report" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/reports/report_form.json b/modules/slack/blocks/block_kit_examples/reports/report_form.json new file mode 100644 index 00000000..ef834782 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/reports/report_form.json @@ -0,0 +1,49 @@ +{ + "title": { + "type": "plain_text", + "text": "OC Slack - Report", + "emoji": true + }, + "type": "modal", + "callback_id": "report_modal", + "blocks": [ + { + "type": "section", + "block_id": "report_title_block", + "text": { + "type": "mrkdwn", + "text": ":warning: Thank you for taking the time to report an issue to the moderation team. Please fill out the below input field with the text of the message you'd like to report. If you'd like, you can include a short description of why you are reporting it. The report will only be shown to the moderators of the OC Slack workspace.:warning:" + } + }, + { + "type": "input", + "block_id": "report_input", + "element": { + "type": "plain_text_input", + "action_id": "report_input_field", + "multiline": true, + "focus_on_load": true, + "min_length": 2, + "placeholder": { + "type": "plain_text", + "text": "You can copy and paste the text of the message you'd like to report or tell us a bit about what you are reporting..." + } + }, + "label": { + "type": "plain_text", + "text": "Text of message you are reporting or reason for your report*", + "emoji": true + } + } + ], + "close": { + "type": "plain_text", + "text": "Cancel", + "emoji": true + }, + "submit": { + "type": "plain_text", + "text": "Submit Report", + "emoji": true + } +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_failed_report.json b/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_failed_report.json new file mode 100644 index 00000000..c5280194 --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_failed_report.json @@ -0,0 +1,11 @@ +{ + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":warning: Your report was not sent to the moderators due to an unspecified error. Please contact @moderators and let them know so we can investigate the issue and take care of your report. :warning:" + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_successful_report.json b/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_successful_report.json new file mode 100644 index 00000000..55abe9ac --- /dev/null +++ b/modules/slack/blocks/block_kit_examples/reports/response_to_user_on_successful_report.json @@ -0,0 +1,11 @@ +{ + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":white_check_mark: Your report has been received by the moderator team and someone will be reaching out shortly! Please don't hesitate to contact @moderators if you have any other questions. :white_check_mark:" + } + } + ] +} \ No newline at end of file diff --git a/modules/slack/blocks/greeting_blocks.py b/modules/slack/blocks/greeting_blocks.py new file mode 100644 index 00000000..72c07ac5 --- /dev/null +++ b/modules/slack/blocks/greeting_blocks.py @@ -0,0 +1,72 @@ +from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTextObject # noqa: D100 +from slack_sdk.models.blocks.block_elements import ButtonElement +from slack_sdk.models.blocks.blocks import ActionsBlock, SectionBlock + +from modules.models.greeting_models import UserInfo + + +def initial_greet_user_blocks(user_info: UserInfo) -> list[SectionBlock | ActionsBlock]: + """The blocks used for the initial greeting of a new user. + + :param user_info: The user info of the new user. + :return: The blocks used for the initial greeting of a new user. + """ # noqa: D401 + return [ + greeting_blocks_title(user_info.name), + greeting_blocks_user_info(user_info), + greeting_block_button(user_info.id), + ] + + +def greeting_blocks_title(slack_name: str) -> SectionBlock: # noqa: D103 + greeting_text = MarkdownTextObject( + text=f"🎉 <@{slack_name}> has joined our community! 🎉", + ) + return SectionBlock(block_id="title_text", text=greeting_text) + + +def greeting_blocks_user_info(user_info: UserInfo) -> SectionBlock: # noqa: D103 + greeting_fields = [] + for key, value in user_info.__dict__.items(): + if key in ("zip_code", "email", "id"): # noqa: SIM114 + pass + elif value is None: + pass + else: + greeting_fields.append( + MarkdownTextObject(text=f"*{key.replace('_', ' ').title()}:*"), + ) + greeting_fields.append(MarkdownTextObject(text=f"{value}")) + return SectionBlock(block_id="user_info", fields=greeting_fields) + + +def greeting_block_button(new_user_id: str) -> ActionsBlock: # noqa: D103 + button_text = PlainTextObject(text="I will greet them!", emoji=True) + greet_button = ButtonElement( + text=button_text, + action_id="greet_new_user_claim", + style="primary", + value=f"{new_user_id}", + ) + return ActionsBlock( + block_id="claim_action", + elements=[greet_button], + ) + + +def greeting_block_claimed_button(claiming_user_name: str) -> ActionsBlock: + """Creates an ActionsBlock that contains a button showing who claimed the greeting - this button allows anyone to reset the claim. + + :param claiming_user_name: username of the user claiming the greeting + :type claiming_user_name: str + :return: an ActionsBlock with the claimed button that allows a reset + :rtype: ActionsBlock + """ # noqa: E501, D401 + button_text = PlainTextObject(text=f"Greeted by {claiming_user_name}!") + claimed_greet_button = ButtonElement( + text=button_text, + action_id="reset_greet_new_user_claim", + style="danger", + value=f"{claiming_user_name}", + ) + return ActionsBlock(block_id="reset_claim_action", elements=[claimed_greet_button]) diff --git a/modules/slack/blocks/mentorship_blocks.py b/modules/slack/blocks/mentorship_blocks.py new file mode 100644 index 00000000..4728b891 --- /dev/null +++ b/modules/slack/blocks/mentorship_blocks.py @@ -0,0 +1,281 @@ +import logging # noqa: D100 + +from slack_sdk.models.blocks.basic_components import ( + MarkdownTextObject, + Option, + PlainTextObject, +) +from slack_sdk.models.blocks.block_elements import ( + ButtonElement, + PlainTextInputElement, + StaticMultiSelectElement, + StaticSelectElement, +) +from slack_sdk.models.blocks.blocks import ActionsBlock, Block, DividerBlock, InputBlock, SectionBlock +from slack_sdk.models.views import View + +from modules.airtable import message_text_table +from modules.models.mentorship_models import ( + MentorshipAffiliation, + MentorshipService, + MentorshipSkillset, +) +from modules.slack.blocks import shared_blocks + +logger = logging.getLogger(__name__) + + +def mentorship_request_view( # noqa: D103 + services: list[MentorshipService], + skillsets: list[MentorshipSkillset], + affiliations: list[MentorshipAffiliation], +) -> View: + logger.info("STAGE: Building mentorship request form view...") + return View( + type="modal", + callback_id="mentorship_request_form_submit", + title=PlainTextObject(text="OC Mentor Request", emoji=True), + submit=PlainTextObject(text="Submit Request", emoji=True), + cancel=PlainTextObject(text="Cancel", emoji=True), + external_id="mentorship_request_form_modal", + blocks=mentorship_request_blocks(services, skillsets, affiliations), + ) + + +def mentorship_request_blocks( + services: list[MentorshipService], + skillsets: list[MentorshipSkillset], + affiliations: list[MentorshipAffiliation], +) -> list[SectionBlock | DividerBlock | InputBlock]: + """The blocks used for the mentorship request form. + + :param services: The list of mentorship services. + :param skillsets: The list of mentorship skillsets. + :param affiliations: The affiliations of the requestor. + :return: The blocks used for the mentorship request form. + """ # noqa: D401 + logger.info("STAGE: Building the mentorship request blocks...") + messages = message_text_table.retrieve_valid_messages_by_view( + "Valid Mentorship Requests", + ) + return [ + request_view_main_text(messages["mentorship_request_main"].text), + shared_blocks.generic_divider_block(block_id="mentorship_request_divider_1"), + request_view_services_input( + services, + messages["mentorship_request_service_label"].text, + messages["mentorship_request_service_placeholder"].text, + ), + request_view_skillsets_input( + skillsets, + messages["mentorship_request_skillset_label"].text, + messages["mentorship_request_skillset_placeholder"].text, + ), + request_view_details_input( + messages["mentorship_request_details_label"].text, + messages["mentorship_request_details_placeholder"].text, + ), + shared_blocks.generic_divider_block(block_id="mentorship_request_divider_2"), + request_view_affiliations_input( + affiliations, + messages["mentorship_request_affiliation_label"].text, + messages["mentorship_request_affiliation_placeholder"].text, + ), + ] + + +def request_view_main_text(main_text: str) -> SectionBlock: # noqa: D103 + logger.info("STAGE: Building mentorship request form main section block...") + return SectionBlock( + block_id="mentorship_request_main_text", + text=MarkdownTextObject(text=main_text), + ) + + +def request_view_services_input( # noqa: D103 + services: list[MentorshipService], + services_label: str, + services_placeholder: str, +) -> InputBlock: + logger.info("STAGE: Building mentorship request form services input block...") + service_options = [Option(label=service.name, value=service.name) for service in services] + input_element = StaticSelectElement( + placeholder=PlainTextObject(text=services_placeholder, emoji=True), + action_id="mentorship_service_selection", + options=service_options, + ) + return InputBlock( + block_id="mentorship_service_input", + label=PlainTextObject(text=services_label, emoji=True), + element=input_element, + ) + + +def request_view_skillsets_input( # noqa: D103 + skillsets: list[MentorshipSkillset], + skillsets_label: str, + skillsets_placeholder: str, +) -> InputBlock: + logger.info("STAGE: Building mentorship request form skillsets input block...") + service_options = [Option(label=skillset.name, value=skillset.name) for skillset in skillsets] + input_element = StaticMultiSelectElement( + placeholder=PlainTextObject(text=skillsets_placeholder, emoji=True), + action_id="mentorship_skillset_multi_selection", + options=service_options, + ) + return InputBlock( + block_id="mentor_skillset_input", + label=PlainTextObject(text=skillsets_label, emoji=True), + element=input_element, + ) + + +def request_view_details_input( # noqa: D103 + details_label: str, + details_placeholder: str, +) -> InputBlock: + logger.info("STAGE: Building mentorship request form details input block...") + input_element = PlainTextInputElement( + action_id="details_text_input", + multiline=True, + min_length=10, + placeholder=PlainTextObject(text=details_placeholder, emoji=True), + ) + return InputBlock( + block_id="details_input_block", + label=PlainTextObject(text=details_label, emoji=True), + element=input_element, + ) + + +def request_view_affiliations_input( # noqa: D103 + affiliations: list[MentorshipAffiliation], + affiliations_label: str, + affiliations_placeholder: str, +) -> InputBlock: + logger.info("STAGE: Building mentorship request form affiliations input block...") + affiliation_options = [Option(label=affiliation.name, value=affiliation.name) for affiliation in affiliations] + input_element = StaticSelectElement( + placeholder=PlainTextObject(text=affiliations_placeholder, emoji=True), + action_id="mentorship_affiliation_selection", + options=affiliation_options, + ) + return InputBlock( + block_id="mentorship_affiliation_input", + label=PlainTextObject(text=affiliations_label, emoji=True), + element=input_element, + ) + + +def request_successful_block() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_received_successfully", + ) + return SectionBlock( + block_id="mentorship_request_received_successfully", + text=MarkdownTextObject(text=message_row.text), + ) + + +def request_unsuccessful_block() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_unsuccessful", + ) + return SectionBlock( + block_id="mentorship_request_unsuccessful", + text=MarkdownTextObject(text=message_row.text), + ) + + +def request_claim_blocks( # noqa: D103 + requested_service: str, + skillsets: list[str], + affiliation: str, + requesting_username: str, +) -> list[Block]: + return [ + request_claim_service_block(requesting_username, requested_service), + request_claim_skillset_block(skillsets), + request_claim_affiliation_block(affiliation), + request_claim_button(), + ] + + +def request_claim_service_block( # noqa: D103 + requesting_username: str, + requested_service: str, +) -> SectionBlock: + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_claim_service_text", + ) + return SectionBlock( + block_id="mentorship_request_service_text", + text=MarkdownTextObject( + text=message_row.text.format(requesting_username, requested_service), + ), + ) + + +def request_claim_skillset_block(skillsets: list[str]) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_claim_skillset_text", + ) + return SectionBlock( + block_id="mentorship_request_skillset_text", + text=MarkdownTextObject(text=message_row.text.format(", ".join(skillsets))), + ) + + +def request_claim_affiliation_block(affiliation: str) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_claim_affiliation_text", + ) + return SectionBlock( + block_id="mentorship_request_affiliation_text", + text=MarkdownTextObject(text=message_row.text.format(affiliation)), + ) + + +def request_claim_button() -> ActionsBlock: # noqa: D103 + button_element = ButtonElement( + text=PlainTextObject(text="Claim Mentorship Request", emoji=True), + style="primary", + action_id="claim_mentorship_request", + ) + return ActionsBlock(block_id="claim_button_action_block", elements=[button_element]) + + +def request_claim_reset_button(claiming_username: str) -> ActionsBlock: # noqa: D103 + button_element = ButtonElement( + text=PlainTextObject( + text=f"Request Claimed By {claiming_username}", + emoji=True, + ), + style="danger", + action_id="reset_mentorship_request_claim", + ) + return ActionsBlock(block_id="claim_button_action_block", elements=[button_element]) + + +def request_claim_details_block(details: str) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_claim_details_text", + ) + return SectionBlock( + block_id="mentorship_request_details_text", + text=MarkdownTextObject(text=message_row.text.format(details)), + ) + + +def request_claim_tagged_users_block(usernames: list[str]) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="mentorship_request_claim_tagged_users", + ) + return SectionBlock( + block_id="mentorship_request_tagged_users", + text=MarkdownTextObject( + text=message_row.text.format( + " ".join([f"<@{username}>" for username in usernames]), + ), + ), + ) diff --git a/modules/slack/blocks/new_join_blocks.py b/modules/slack/blocks/new_join_blocks.py new file mode 100644 index 00000000..3044be7e --- /dev/null +++ b/modules/slack/blocks/new_join_blocks.py @@ -0,0 +1,133 @@ +from slack_sdk.models.blocks import ( # noqa: D100 + Block, + ButtonElement, + MarkdownTextObject, + PlainTextObject, + SectionBlock, +) + +from modules.airtable import message_text_table + + +def new_join_immediate_welcome_blocks(joining_username: str) -> list[Block]: # noqa: D103 + return [ + new_join_immediate_welcome_first_text(joining_username), + new_join_immediate_welcome_second_text(), + new_join_immediate_welcome_third_text(), + new_join_immediate_welcome_fourth_text(), + new_join_immediate_welcome_oc_homepage_button(), + new_join_immediate_welcome_slack_download_button(), + new_join_immediate_welcome_oc_coc_button(), + ] + + +def new_join_delayed_welcome_blocks() -> list[Block]: # noqa: D103 + return [ + new_join_delayed_welcome_first_text(), + new_join_immediate_welcome_second_text(), + ] + + +def new_join_immediate_welcome_first_text(joining_username: str) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_immediate_welcome_first_text", + ) + return SectionBlock( + block_id="immediate_welcome_first_text", + text=MarkdownTextObject(text=message_row.text.format(joining_username)), + ) + + +def new_join_immediate_welcome_second_text() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_immediate_welcome_second_text", + ) + return SectionBlock( + block_id="immediate_welcome_second_text", + text=MarkdownTextObject(text=message_row.text), + ) + + +def new_join_immediate_welcome_third_text() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_immediate_welcome_third_text", + ) + return SectionBlock( + block_id="immediate_welcome_third_text", + text=MarkdownTextObject(text=message_row.text), + ) + + +def new_join_immediate_welcome_fourth_text() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_immediate_welcome_fourth_text", + ) + return SectionBlock( + block_id="immediate_welcome_fourth_text", + text=MarkdownTextObject(text=message_row.text), + ) + + +def new_join_immediate_welcome_oc_homepage_button() -> SectionBlock: # noqa: D103 + accessory = ButtonElement( + text=PlainTextObject(text="OC Homepage", emoji=True), + value="oc_home_page", + url="https://operationcode.org/", + action_id="oc_greeting_homepage_click", + style="primary", + ) + return SectionBlock( + block_id="oc_homepage_button", + text=MarkdownTextObject(text="Operation Code Homepage"), + accessory=accessory, + ) + + +def new_join_immediate_welcome_slack_download_button() -> SectionBlock: # noqa: D103 + accessory = ButtonElement( + text=PlainTextObject(text="Slack Download", emoji=True), + value="slack_download", + url="https://slack.com/downloads/", + action_id="oc_greeting_slack_download_click", + style="primary", + ) + return SectionBlock( + block_id="slack_download_button", + text=MarkdownTextObject(text="Slack Download"), + accessory=accessory, + ) + + +def new_join_immediate_welcome_oc_coc_button() -> SectionBlock: # noqa: D103 + accessory = ButtonElement( + text=PlainTextObject(text="Operation Code CoC", emoji=True), + value="operation_code_coc", + url="https://github.com/OperationCode/community/blob/master/code_of_conduct.md", + action_id="oc_greeting_coc_click", + style="primary", + ) + return SectionBlock( + block_id="oc_coc_button", + text=MarkdownTextObject(text="Operation Code CoC"), + accessory=accessory, + ) + + +def new_join_delayed_welcome_first_text() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_delayed_welcome_first_text", + ) + return SectionBlock( + block_id="delayed_welcome_first_text", + text=MarkdownTextObject(text=message_row.text), + ) + + +def new_join_delayed_welcome_second_text() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="new_member_join_delayed_welcome_second_text", + ) + return SectionBlock( + block_id="delayed_welcome_second_text", + text=MarkdownTextObject(text=message_row.text), + ) diff --git a/modules/slack/blocks/report_blocks.py b/modules/slack/blocks/report_blocks.py new file mode 100644 index 00000000..35bedb15 --- /dev/null +++ b/modules/slack/blocks/report_blocks.py @@ -0,0 +1,131 @@ +from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTextObject # noqa: D100 +from slack_sdk.models.blocks.block_elements import ButtonElement, PlainTextInputElement +from slack_sdk.models.blocks.blocks import ( + ActionsBlock, + HeaderBlock, + InputBlock, + SectionBlock, +) +from slack_sdk.models.views import View + +from modules.airtable import message_text_table + + +def report_form_view_elements() -> View: # noqa: D103 + title_text = PlainTextObject(text="OC Slack - Report", emoji=True) + close_button_text = PlainTextObject(text="Cancel") + submit_button_text = PlainTextObject(text="Submit Report") + return View( + type="modal", + callback_id="report_form_submit", + title=title_text, + close=close_button_text, + submit=submit_button_text, + blocks=report_form_modal_blocks(), + external_id="report_form_modal", + ) + + +def report_form_modal_blocks() -> list[SectionBlock | InputBlock]: + """Return the blocks for the report form modal. + + :return: The blocks for the report form modal. + """ + return [report_form_title_block(), report_form_input_block()] + + +def report_form_title_block() -> SectionBlock: # noqa: D103 + text = MarkdownTextObject( + text=":warning: Thank you for taking the time to report an issue to the moderation team. Please fill out the below input field with the text of the message you'd like to report. If you'd like, you can include a short description of why you are reporting it. The report will only be shown to the moderators of the OC Slack workspace.:warning:", # noqa: E501 + ) + return SectionBlock(block_id="report_title_block", text=text) + + +def report_form_input_block() -> InputBlock: # noqa: D103 + input_placeholder = PlainTextObject( + text="You can copy and paste the text of the message you'd like to report or tell us a bit about what you are reporting...", # noqa: E501 + emoji=True, + ) + input_label = PlainTextObject( + text="Text of message you are reporting or reason for your report*", + emoji=True, + ) + text_input = PlainTextInputElement( + action_id="report_input_field", + placeholder=input_placeholder, + focus_on_load=True, + multiline=True, + min_length=2, + ) + return InputBlock(block_id="report_input", element=text_input, label=input_label) + + +def report_claim_blocks( + reporting_user_name: str, + report_details: str, +) -> list[SectionBlock | HeaderBlock | ButtonElement]: + """The blocks used for the report claim form. + + :param reporting_user_name: The username of the user who submitted the report. + :param report_details: The details of the report. + :return: The blocks for the report claim form. + """ # noqa: D401 + return [ + report_claim_title_section(reporting_user_name), + report_claim_details_header(), + report_claim_details(report_details), + report_claim_button(), + ] + + +def report_claim_title_section(username: str) -> SectionBlock: # noqa: D103 + text = MarkdownTextObject( + text=f":warning: <@{username}> has submitted a report. :warning:", + ) + return SectionBlock(text=text, block_id="report_claim_title") + + +def report_claim_details_header() -> HeaderBlock: # noqa: D103 + text = PlainTextObject(text="Report details:", emoji=True) + return HeaderBlock(block_id="report_claim_header", text=text) + + +def report_claim_details(report_details: str) -> SectionBlock: # noqa: D103 + text = MarkdownTextObject(text=f"{report_details}") + return SectionBlock(text=text, block_id="report_claim_details") + + +def report_claim_button() -> ActionsBlock: # noqa: D103 + button_text = PlainTextObject(text="I Will Reach Out to Them") + button_element = ButtonElement( + text=button_text, + style="primary", + action_id="report_claim", + ) + return ActionsBlock(block_id="report_claim_button", elements=[button_element]) + + +def report_claim_claimed_button(claiming_username: str) -> ActionsBlock: # noqa: D103 + button_text = PlainTextObject(text=f"Claimed by {claiming_username}!") + button_element = ButtonElement( + text=button_text, + style="danger", + action_id="reset_report_claim", + ) + return ActionsBlock(block_id="report_claim_button", elements=[button_element]) + + +def report_received_ephemeral_message() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="report_received", + ) + text = MarkdownTextObject(text=message_row.text) + return SectionBlock(block_id="report_received", text=text) + + +def report_failed_ephemeral_message() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="report_not_received", + ) + text = MarkdownTextObject(text=message_row.text) + return SectionBlock(block_id="report_not_received", text=text) diff --git a/modules/slack/blocks/shared_blocks.py b/modules/slack/blocks/shared_blocks.py new file mode 100644 index 00000000..e8f67aec --- /dev/null +++ b/modules/slack/blocks/shared_blocks.py @@ -0,0 +1,74 @@ +from slack_sdk.models.blocks import ( # noqa: D100 + ActionsBlock, + Block, + ButtonElement, + DividerBlock, + MarkdownTextObject, + PlainTextObject, + SectionBlock, +) + +from modules.airtable import message_text_table + + +def generic_divider_block(block_id: str) -> DividerBlock: # noqa: D103 + return DividerBlock(block_id=block_id) + + +def channel_join_request_successful_block(channel_name: str) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="channel_join_request_successful", + ) + return SectionBlock( + block_id="channel_join_request_successful_block", + text=MarkdownTextObject(text=message_row.text.format(channel_name)), + ) + + +def channel_join_request_unsuccessful_block() -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="channel_join_request_unsuccessful", + ) + return SectionBlock( + block_id="channel_join_request_unsuccessful_block", + text=MarkdownTextObject(text=message_row.text), + ) + + +def channel_join_request_blocks(requesting_username: str) -> list[Block]: # noqa: D103 + return [ + channel_join_request_main(requesting_username), + channel_join_request_action(), + ] + + +def channel_join_request_main(requesting_username: str) -> SectionBlock: # noqa: D103 + message_row = message_text_table.retrieve_valid_message_row( + message_slug="channel_join_request_main_text", + ) + return SectionBlock( + block_id="request_main", + text=MarkdownTextObject(text=message_row.text.format(requesting_username)), + ) + + +def channel_join_request_action() -> ActionsBlock: # noqa: D103 + button_element = ButtonElement( + text=PlainTextObject(text="I'll Invite Them!", emoji=True), + style="primary", + action_id="invite_to_channel_click", + ) + return ActionsBlock(block_id="channel_invite_action", elements=[button_element]) + + +def channel_join_request_reset_action(claiming_username: str) -> ActionsBlock: # noqa: D103 + button_text = PlainTextObject(text=f"Invited by {claiming_username}!") + button_element = ButtonElement( + text=button_text, + style="danger", + action_id="reset_channel_invite", + ) + return ActionsBlock( + block_id="reset_channel_invite_action", + elements=[button_element], + ) diff --git a/modules/utils/__init__.py b/modules/utils/__init__.py new file mode 100644 index 00000000..232f112b --- /dev/null +++ b/modules/utils/__init__.py @@ -0,0 +1,172 @@ +"""Utility functions for the Slack app.""" +import logging +import os +from datetime import datetime, timezone +from pathlib import Path +from re import sub + +from dotenv import load_dotenv +from pyairtable import Table +from slack_bolt.app import App +from slack_sdk.models.blocks import MarkdownTextObject, SectionBlock +from slack_sdk.web.async_client import AsyncWebClient + +from modules.models.greeting_models import UserInfo +from modules.models.slack_models.shared_models import ( + SlackConversationInfo, + SlackTeam, + SlackTeamInfo, +) + +logger = logging.getLogger(__name__) +load_dotenv(dotenv_path=str(Path(__file__).parent.parent.parent) + "/.env") + + +def snake_case(string_to_snakecase: str) -> str: + """Snake case a string using regex. + + from https://www.w3resource.com/python-exercises/string/python-data-type-string-exercise-97.php. + + :param string_to_snakecase: The string to be snake-cased. + :return: The snake-cased string. + """ + return "_".join( + sub( + "([A-Z][a-z]+)", + r" \1", + sub("([A-Z]+)", r" \1", string_to_snakecase.replace("-", " ")), + ).split(), + ).lower() + + +def get_team_info() -> SlackTeam: + """Get the team information from Slack. + + Uses a new synchronous Slack app to retrieve the details, so this can happen before the async app is initialized. + + :return: The SlackTeam object. + """ + logger.info("STAGE: Retrieving team information...") + try: + synchronous_app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET"), + ) + team_info = synchronous_app.client.team_info() + conversations = synchronous_app.client.conversations_list( + exclude_archived=True, + types=["public_channel", "private_channel"], + limit=1000, + ) + return SlackTeam( + SlackTeamInfo( + id=team_info["team"]["id"], + name=team_info["team"]["name"], + conversations=[ + SlackConversationInfo(**conversation) for conversation in conversations.data["channels"] + ], + ), + ) + except Exception: + logger.exception("Failed to retrieve team information.") + raise + finally: + del synchronous_app + + +async def get_slack_user_from_email(client: AsyncWebClient, email: str) -> UserInfo: + """Retrieve a Slack user from Slack using email. + + :param client: The Slack client. + :param email: The email address of the user. + :return: The UserInfo object. + """ + slack_user = await client.users_lookupByEmail(email=email) + return UserInfo( + **slack_user.data["user"], + email=slack_user.data["user"]["profile"]["email"], + ) + + +async def get_slack_user_by_id(client: AsyncWebClient, user_id: str) -> UserInfo: + """Retrieve a Slack user from Slack using the Slack user ID. + + :param client: The Slack client. + :param user_id: The Slack user ID. + :return: The UserInfo object. + """ + slack_user = await client.users_info(user=user_id) + return UserInfo( + **slack_user.data["user"], + email=slack_user.data["user"]["profile"]["email"], + ) + + +async def log_to_thread( # noqa: PLR0913 - too many arguments + client: AsyncWebClient, + channel_id: str, + message_ts: str, + username: str, + action_ts: str, + claim: bool, # noqa: FBT001 +) -> None: + """Log a claim or reset to a thread. + + :param client: The Slack client. + :param channel_id: The channel ID of the message. + :param message_ts: The timestamp of the message. + :param username: The username of the user performing the action. + :param action_ts: The timestamp of the action. + :param claim: Whether it's a claim or reset action. + """ + await client.chat_postMessage( + channel=channel_id, + thread_ts=message_ts, + text="Logging to greeting thread...", + blocks=[threaded_action_logging(username, action_ts, claim)], + ) + + +def threaded_action_logging(username: str, timestamp: str, claim: bool) -> SectionBlock: # noqa: FBT001 + """Return a block that is used to log a claim or reset to a thread. + + :param username: username of the user performing the action + :type username: str + :param timestamp: string timestamp of the action in Unix Epoch Time + :type timestamp: str + :param claim: whether it's a claim action or not + :type claim: bool + :return: a section block to be threaded on the original message + :rtype: SectionBlock + """ + if claim: + text = MarkdownTextObject( + text=f"Claimed by {username} at " + f"{datetime.fromtimestamp(float(timestamp), tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC!", + ) + else: + text = MarkdownTextObject( + text=f"Reset by {username} at " + f"{datetime.fromtimestamp(float(timestamp), tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC!", + ) + return SectionBlock(block_id="greeting_log_reply", text=text) + + +def table_fields(table: Table) -> list[str]: + """Return snake cased columns (fields in Airtable parlance) on the table. + + Because we don't have access to the Airtable metadata API, we must set up a view on every table with every column + filled in since as the Airtable API says - "Returned records do not include any fields with "empty" + values, e.g. "", [], or false.". + + :return: List of fields on the table. + """ + try: + first_record = table.first(view="Fields") + return [snake_case(field) for field in first_record["fields"]] + except Exception: + logger.exception("Unable to retrieve fields from Airtable.") + raise + + +slack_team = get_team_info() diff --git a/modules/utils/daily_programmer_scheduler.py b/modules/utils/daily_programmer_scheduler.py new file mode 100644 index 00000000..ca697ff9 --- /dev/null +++ b/modules/utils/daily_programmer_scheduler.py @@ -0,0 +1,14 @@ +"""Module containing the daily programmer scheduler task.""" +import logging + +from slack_bolt.async_app import AsyncApp + +logger = logging.getLogger(__name__) + + +async def post_daily_programmer_message(async_app: AsyncApp) -> None: # noqa: ARG001 - temporary + """Post the daily programmer message to the #daily-programmer channel. + + :param async_app: The Slack Bolt async application. + """ + logger.info("STAGE: Beginning task post_daily_programmer_message...") diff --git a/modules/utils/example_requests/mentorship_request_claim_action.json b/modules/utils/example_requests/mentorship_request_claim_action.json new file mode 100644 index 00000000..b246c8d9 --- /dev/null +++ b/modules/utils/example_requests/mentorship_request_claim_action.json @@ -0,0 +1,110 @@ +{ + "type": "block_actions", + "user": { + "id": "U01RN31JSTD", + "username": "judson.stevens", + "name": "judson.stevens", + "team_id": "T01SBLCQ57A" + }, + "api_app_id": "A02R6C6S9JN", + "token": "ZdW4MAeWALbwTKtzdfhyvrGW", + "container": { + "type": "message", + "message_ts": "1640986475.006500", + "channel_id": "C01R77KM8H5", + "is_ephemeral": false, + "thread_ts": "1640986475.006500" + }, + "trigger_id": "2921169898672.1895692821248.8f353dfbf0be6d23ed09354b247e0b89", + "team": { + "id": "T01SBLCQ57A", + "domain": "bot-testing-field" + }, + "enterprise": "None", + "is_enterprise_install": false, + "channel": { + "id": "C01R77KM8H5", + "name": "mentors-internal" + }, + "message": { + "bot_id": "B02QRQ4KU5V", + "type": "message", + "text": "New mentorship request received...", + "user": "U02RK2AL5LZ", + "ts": "1640986475.006500", + "team": "T01SBLCQ57A", + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_service_text", + "text": { + "type": "mrkdwn", + "text": "User <@U01RN31JSTD> has requested a mentor for General Guidance.", + "verbatim": false + } + }, + { + "type": "section", + "block_id": "mentorship_request_skillset_text", + "text": { + "type": "mrkdwn", + "text": "*Requested Skillset(s):* Architecture, Career Advice, Cloud / AWS / GCP / Azure", + "verbatim": false + } + }, + { + "type": "section", + "block_id": "mentorship_request_affiliation_text", + "text": { + "type": "mrkdwn", + "text": "*Requestor Affiliation:* US Military Spouse", + "verbatim": false + } + }, + { + "type": "actions", + "block_id": "claim_button_action_block", + "elements": [ + { + "type": "button", + "action_id": "claim_mentorship_request", + "text": { + "type": "plain_text", + "text": "Claim Mentorship Request", + "emoji": true + }, + "style": "primary" + } + ] + } + ], + "thread_ts": "1640986475.006500", + "reply_count": 2, + "reply_users_count": 1, + "latest_reply": "1640986477.006800", + "reply_users": [ + "U02RK2AL5LZ" + ], + "is_locked": false, + "subscribed": true, + "last_read": "1640986477.006800" + }, + "state": { + "values": {} + }, + "response_url": "https://hooks.slack.com/actions/T01SBLCQ57A/2899731511204/xb8gxI24ldtCaVwbdsddM0nb", + "actions": [ + { + "action_id": "claim_mentorship_request", + "block_id": "claim_button_action_block", + "text": { + "type": "plain_text", + "text": "Claim Mentorship Request", + "emoji": true + }, + "style": "primary", + "type": "button", + "action_ts": "1640986929.354736" + } + ] +} diff --git a/modules/utils/example_requests/pride_request_command.json b/modules/utils/example_requests/pride_request_command.json new file mode 100644 index 00000000..bbd33174 --- /dev/null +++ b/modules/utils/example_requests/pride_request_command.json @@ -0,0 +1,14 @@ +{ + "token": "ZdW4MAeWALbwTKtzdfhyvrGW", + "team_id": "T01SBLCQ57A", + "team_domain": "bot-testing-field", + "channel_id": "D02R6CR6DMG", + "channel_name": "directmessage", + "user_id": "U01RN31JSTD", + "user_name": "judson.stevens", + "command": "/pride", + "api_app_id": "A02R6C6S9JN", + "is_enterprise_install": "false", + "response_url": "https://hooks.slack.com/commands/T01SBLCQ57A/2897652965298/c11hovXK7EMpPtnWOcRvFE4m", + "trigger_id": "2921387427888.1895692821248.08d2864bfc4f49d666e25ead52fb95ca" +} \ No newline at end of file diff --git a/modules/utils/example_requests/view_submission_request.json b/modules/utils/example_requests/view_submission_request.json new file mode 100644 index 00000000..761414d4 --- /dev/null +++ b/modules/utils/example_requests/view_submission_request.json @@ -0,0 +1,737 @@ +{ + "type": "view_submission", + "team": { + "id": "T01SBLCQ57A", + "domain": "bot-testing-field" + }, + "user": { + "id": "U01RN31JSTD", + "username": "judson.stevens", + "name": "judson.stevens", + "team_id": "T01SBLCQ57A" + }, + "api_app_id": "A02R6C6S9JN", + "token": "ZdW4MAeWALbwTKtzdfhyvrGW", + "trigger_id": "2908311897777.1895692821248.89b9cdcdf45940f3a9ab14c5ff527489", + "view": { + "id": "V02S8ML6H8D", + "team_id": "T01SBLCQ57A", + "type": "modal", + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_main_text", + "text": { + "type": "mrkdwn", + "text": ":male-teacher: Mentor Request Form :female-teacher:\\n Thank you for signing up for a mentoring session. Please fill out the form below. If you have any questions, please don't hesitate to contact @mentor-coordinators. *NOTE*: Mentors are typically available within a couple hours; however, on weekends or holidays the timeframe may be longer before someone reaches out.", + "verbatim": false + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_1" + }, + { + "type": "input", + "block_id": "mentorship_service_input", + "label": { + "type": "plain_text", + "text": "Mentorship Service*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "static_select", + "action_id": "mentorship_service_selection", + "placeholder": { + "type": "plain_text", + "text": "Type of service...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "General Guidance", + "emoji": true + }, + "value": "general_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Review", + "emoji": true + }, + "value": "resume_review" + }, + { + "text": { + "type": "plain_text", + "text": "Career Guidance", + "emoji": true + }, + "value": "career_guidance" + } + ] + } + }, + { + "type": "input", + "block_id": "mentor_skillset_input", + "label": { + "type": "plain_text", + "text": "Skillsets Related to Your Request", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "multi_static_select", + "action_id": "mentorship_skillset_multi_selection", + "placeholder": { + "type": "plain_text", + "text": "Skills related to your request...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Architecture", + "emoji": true + }, + "value": "architecture" + }, + { + "text": { + "type": "plain_text", + "text": "Bot Programming", + "emoji": true + }, + "value": "bot_programming" + }, + { + "text": { + "type": "plain_text", + "text": "C / C++", + "emoji": true + }, + "value": "c_c_plus_plus" + }, + { + "text": { + "type": "plain_text", + "text": "C# / .NET", + "emoji": true + }, + "value": "c_sharp_dotnet" + }, + { + "text": { + "type": "plain_text", + "text": "Cake", + "emoji": true + }, + "value": "cake" + }, + { + "text": { + "type": "plain_text", + "text": "Career Advice", + "emoji": true + }, + "value": "career_advice" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / Azure", + "emoji": true + }, + "value": "cloud_aws_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / GCP / Azure", + "emoji": true + }, + "value": "cloud_aws_gcp_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Cyber-security", + "emoji": true + }, + "value": "cyber_security" + }, + { + "text": { + "type": "plain_text", + "text": "Cybersecurity", + "emoji": true + }, + "value": "cybersecurity" + }, + { + "text": { + "type": "plain_text", + "text": "Data Science", + "emoji": true + }, + "value": "data_science" + }, + { + "text": { + "type": "plain_text", + "text": "Design / UX", + "emoji": true + }, + "value": "design_ux" + }, + { + "text": { + "type": "plain_text", + "text": "DevOps", + "emoji": true + }, + "value": "devops" + }, + { + "text": { + "type": "plain_text", + "text": "Docker / Containers", + "emoji": true + }, + "value": "docker_containers" + }, + { + "text": { + "type": "plain_text", + "text": "Education Help", + "emoji": true + }, + "value": "education_help" + }, + { + "text": { + "type": "plain_text", + "text": "Entrepreneurship", + "emoji": true + }, + "value": "entrepreneurship" + }, + { + "text": { + "type": "plain_text", + "text": "Functional Programming", + "emoji": true + }, + "value": "functional_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Funding", + "emoji": true + }, + "value": "funding" + }, + { + "text": { + "type": "plain_text", + "text": "General Architecture", + "emoji": true + }, + "value": "general_architecture" + }, + { + "text": { + "type": "plain_text", + "text": "General Code Review", + "emoji": true + }, + "value": "general_code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Go", + "emoji": true + }, + "value": "go" + }, + { + "text": { + "type": "plain_text", + "text": "Introductory Coding", + "emoji": true + }, + "value": "introductory_coding" + }, + { + "text": { + "type": "plain_text", + "text": "Java", + "emoji": true + }, + "value": "java" + }, + { + "text": { + "type": "plain_text", + "text": "JavaScript", + "emoji": true + }, + "value": "javascript" + }, + { + "text": { + "type": "plain_text", + "text": "Job Search", + "emoji": true + }, + "value": "job_search" + }, + { + "text": { + "type": "plain_text", + "text": "Linux", + "emoji": true + }, + "value": "linux" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (Android)", + "emoji": true + }, + "value": "mobile_development_android" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (iOS)", + "emoji": true + }, + "value": "mobile_development_ios" + }, + { + "text": { + "type": "plain_text", + "text": "Mock Interview", + "emoji": true + }, + "value": "mock_interview" + }, + { + "text": { + "type": "plain_text", + "text": "Networking", + "emoji": true + }, + "value": "networking" + }, + { + "text": { + "type": "plain_text", + "text": "Open Source Contributing", + "emoji": true + }, + "value": "open_source_contributing" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "PHP", + "emoji": true + }, + "value": "php" + }, + { + "text": { + "type": "plain_text", + "text": "Product Management", + "emoji": true + }, + "value": "product_management" + }, + { + "text": { + "type": "plain_text", + "text": "Project Management", + "emoji": true + }, + "value": "project_management" + }, + { + "text": { + "type": "plain_text", + "text": "Prolog", + "emoji": true + }, + "value": "prolog" + }, + { + "text": { + "type": "plain_text", + "text": "Python", + "emoji": true + }, + "value": "python" + }, + { + "text": { + "type": "plain_text", + "text": "React", + "emoji": true + }, + "value": "react" + }, + { + "text": { + "type": "plain_text", + "text": "React / Angular / Vue", + "emoji": true + }, + "value": "react_angular_vue" + }, + { + "text": { + "type": "plain_text", + "text": "Remote Working", + "emoji": true + }, + "value": "remote_working" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Reviews", + "emoji": true + }, + "value": "resume_reviews" + }, + { + "text": { + "type": "plain_text", + "text": "Ruby / Rails", + "emoji": true + }, + "value": "ruby_rails" + }, + { + "text": { + "type": "plain_text", + "text": "Rust", + "emoji": true + }, + "value": "rust" + }, + { + "text": { + "type": "plain_text", + "text": "Salesforce", + "emoji": true + }, + "value": "salesforce" + }, + { + "text": { + "type": "plain_text", + "text": "Scala", + "emoji": true + }, + "value": "scala" + }, + { + "text": { + "type": "plain_text", + "text": "SQL", + "emoji": true + }, + "value": "sql" + }, + { + "text": { + "type": "plain_text", + "text": "Study Help", + "emoji": true + }, + "value": "study_help" + }, + { + "text": { + "type": "plain_text", + "text": "Transition Assistance", + "emoji": true + }, + "value": "transition_assistance" + }, + { + "text": { + "type": "plain_text", + "text": "VA Benefits / Financial Aid", + "emoji": true + }, + "value": "va_benefits_financial_aid" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Back-end)", + "emoji": true + }, + "value": "web_development_back_end" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Front-end)", + "emoji": true + }, + "value": "web_development_front_end" + }, + { + "text": { + "type": "plain_text", + "text": "Xamarin", + "emoji": true + }, + "value": "xamarin" + } + ] + } + }, + { + "type": "input", + "block_id": "details_input_block", + "label": { + "type": "plain_text", + "text": "Request Details*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "plain_text_input", + "action_id": "details_text_input", + "placeholder": { + "type": "plain_text", + "text": "Please enter more information about your request. The more specific you are the easier it is to match you with a great mentor!", + "emoji": true + }, + "multiline": true, + "min_length": 10, + "dispatch_action_config": { + "trigger_actions_on": [ + "on_enter_pressed" + ] + } + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_2" + }, + { + "type": "input", + "block_id": "mentorship_affiliation_input", + "label": { + "type": "plain_text", + "text": "I certify that I am affiliated with one of the following groups:*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "static_select", + "action_id": "mentorship_affiliation_selection", + "placeholder": { + "type": "plain_text", + "text": "Please select a group...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "US Veteran", + "emoji": true + }, + "value": "us_veteran" + }, + { + "text": { + "type": "plain_text", + "text": "US Military Spouse", + "emoji": true + }, + "value": "us_military_spouse" + }, + { + "text": { + "type": "plain_text", + "text": "US Active Duty", + "emoji": true + }, + "value": "us_active_duty" + }, + { + "text": { + "type": "plain_text", + "text": "Non-Military", + "emoji": true + }, + "value": "non_military" + }, + { + "text": { + "type": "plain_text", + "text": "Foreign Military", + "emoji": true + }, + "value": "foreign_military" + } + ] + } + } + ], + "private_metadata": "", + "callback_id": "mentorship_request_form_submit", + "state": { + "values": { + "mentorship_service_input": { + "mentorship_service_selection": { + "type": "static_select", + "selected_option": { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + } + } + }, + "mentor_skillset_input": { + "mentorship_skillset_multi_selection": { + "type": "multi_static_select", + "selected_options": [ + { + "text": { + "type": "plain_text", + "text": "Bot Programming", + "emoji": true + }, + "value": "bot_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Career Advice", + "emoji": true + }, + "value": "career_advice" + }, + { + "text": { + "type": "plain_text", + "text": "Prolog", + "emoji": true + }, + "value": "prolog" + }, + { + "text": { + "type": "plain_text", + "text": "Python", + "emoji": true + }, + "value": "python" + } + ] + } + }, + "details_input_block": { + "details_text_input": { + "type": "plain_text_input", + "value": "Testing the details screen" + } + }, + "mentorship_affiliation_input": { + "mentorship_affiliation_selection": { + "type": "static_select", + "selected_option": { + "text": { + "type": "plain_text", + "text": "US Veteran", + "emoji": true + }, + "value": "us_veteran" + } + } + } + } + }, + "hash": "1640903702.u8C2NM3Y", + "title": { + "type": "plain_text", + "text": "OC Mentor Request", + "emoji": true + }, + "clear_on_close": false, + "notify_on_close": false, + "close": "None", + "submit": { + "type": "plain_text", + "text": "Submit Request", + "emoji": true + }, + "previous_view_id": "None", + "root_view_id": "V02S8ML6H8D", + "app_id": "A02R6C6S9JN", + "external_id": "mentorship_request_form_modal", + "app_installed_team_id": "T01SBLCQ57A", + "bot_id": "B02QRQ4KU5V" + }, + "response_urls": [], + "is_enterprise_install": false, + "enterprise": "None" +} \ No newline at end of file diff --git a/modules/utils/example_responses/view_open_response.json b/modules/utils/example_responses/view_open_response.json new file mode 100644 index 00000000..cd1dab65 --- /dev/null +++ b/modules/utils/example_responses/view_open_response.json @@ -0,0 +1,649 @@ +{ + "ok": true, + "view": { + "id": "V02S57SESLW", + "team_id": "T01SBLCQ57A", + "type": "modal", + "blocks": [ + { + "type": "section", + "block_id": "mentorship_request_main_text", + "text": { + "type": "mrkdwn", + "text": ":male-teacher: Mentor Request Form :female-teacher:\\n Thank you for signing up for a mentoring session. Please fill out the form below. If you have any questions, please don't hesitate to contact @mentor-coordinators. *NOTE*: Mentors are typically available within a couple hours; however, on weekends or holidays the timeframe may be longer before someone reaches out.", + "verbatim": false + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_1" + }, + { + "type": "input", + "block_id": "mentorship_service_input", + "label": { + "type": "plain_text", + "text": "Mentorship Service*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "static_select", + "action_id": "mentorship_service_selection", + "placeholder": { + "type": "plain_text", + "text": "Type of service...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "General Guidance", + "emoji": true + }, + "value": "general_guidance" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Review", + "emoji": true + }, + "value": "resume_review" + }, + { + "text": { + "type": "plain_text", + "text": "Career Guidance", + "emoji": true + }, + "value": "career_guidance" + } + ] + } + }, + { + "type": "input", + "block_id": "mentor_skillset_input", + "label": { + "type": "plain_text", + "text": "Skillsets Related to Your Request", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "multi_static_select", + "action_id": "mentorship_skillset_multi_selection", + "placeholder": { + "type": "plain_text", + "text": "Skills related to your request...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Architecture", + "emoji": true + }, + "value": "architecture" + }, + { + "text": { + "type": "plain_text", + "text": "Bot Programming", + "emoji": true + }, + "value": "bot_programming" + }, + { + "text": { + "type": "plain_text", + "text": "C / C++", + "emoji": true + }, + "value": "c_c_plus_plus" + }, + { + "text": { + "type": "plain_text", + "text": "C# / .NET", + "emoji": true + }, + "value": "c_sharp_dotnet" + }, + { + "text": { + "type": "plain_text", + "text": "Cake", + "emoji": true + }, + "value": "cake" + }, + { + "text": { + "type": "plain_text", + "text": "Career Advice", + "emoji": true + }, + "value": "career_advice" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / Azure", + "emoji": true + }, + "value": "cloud_aws_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Cloud / AWS / GCP / Azure", + "emoji": true + }, + "value": "cloud_aws_gcp_azure" + }, + { + "text": { + "type": "plain_text", + "text": "Code Review", + "emoji": true + }, + "value": "code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Cyber-security", + "emoji": true + }, + "value": "cyber_security" + }, + { + "text": { + "type": "plain_text", + "text": "Cybersecurity", + "emoji": true + }, + "value": "cybersecurity" + }, + { + "text": { + "type": "plain_text", + "text": "Data Science", + "emoji": true + }, + "value": "data_science" + }, + { + "text": { + "type": "plain_text", + "text": "Design / UX", + "emoji": true + }, + "value": "design_ux" + }, + { + "text": { + "type": "plain_text", + "text": "DevOps", + "emoji": true + }, + "value": "devops" + }, + { + "text": { + "type": "plain_text", + "text": "Docker / Containers", + "emoji": true + }, + "value": "docker_containers" + }, + { + "text": { + "type": "plain_text", + "text": "Education Help", + "emoji": true + }, + "value": "education_help" + }, + { + "text": { + "type": "plain_text", + "text": "Entrepreneurship", + "emoji": true + }, + "value": "entrepreneurship" + }, + { + "text": { + "type": "plain_text", + "text": "Functional Programming", + "emoji": true + }, + "value": "functional_programming" + }, + { + "text": { + "type": "plain_text", + "text": "Funding", + "emoji": true + }, + "value": "funding" + }, + { + "text": { + "type": "plain_text", + "text": "General Architecture", + "emoji": true + }, + "value": "general_architecture" + }, + { + "text": { + "type": "plain_text", + "text": "General Code Review", + "emoji": true + }, + "value": "general_code_review" + }, + { + "text": { + "type": "plain_text", + "text": "Go", + "emoji": true + }, + "value": "go" + }, + { + "text": { + "type": "plain_text", + "text": "Introductory Coding", + "emoji": true + }, + "value": "introductory_coding" + }, + { + "text": { + "type": "plain_text", + "text": "Java", + "emoji": true + }, + "value": "java" + }, + { + "text": { + "type": "plain_text", + "text": "JavaScript", + "emoji": true + }, + "value": "javascript" + }, + { + "text": { + "type": "plain_text", + "text": "Job Search", + "emoji": true + }, + "value": "job_search" + }, + { + "text": { + "type": "plain_text", + "text": "Linux", + "emoji": true + }, + "value": "linux" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (Android)", + "emoji": true + }, + "value": "mobile_development_android" + }, + { + "text": { + "type": "plain_text", + "text": "Mobile Development (iOS)", + "emoji": true + }, + "value": "mobile_development_ios" + }, + { + "text": { + "type": "plain_text", + "text": "Mock Interview", + "emoji": true + }, + "value": "mock_interview" + }, + { + "text": { + "type": "plain_text", + "text": "Networking", + "emoji": true + }, + "value": "networking" + }, + { + "text": { + "type": "plain_text", + "text": "Open Source Contributing", + "emoji": true + }, + "value": "open_source_contributing" + }, + { + "text": { + "type": "plain_text", + "text": "Pair Programming", + "emoji": true + }, + "value": "pair_programming" + }, + { + "text": { + "type": "plain_text", + "text": "PHP", + "emoji": true + }, + "value": "php" + }, + { + "text": { + "type": "plain_text", + "text": "Product Management", + "emoji": true + }, + "value": "product_management" + }, + { + "text": { + "type": "plain_text", + "text": "Project Management", + "emoji": true + }, + "value": "project_management" + }, + { + "text": { + "type": "plain_text", + "text": "Prolog", + "emoji": true + }, + "value": "prolog" + }, + { + "text": { + "type": "plain_text", + "text": "Python", + "emoji": true + }, + "value": "python" + }, + { + "text": { + "type": "plain_text", + "text": "React", + "emoji": true + }, + "value": "react" + }, + { + "text": { + "type": "plain_text", + "text": "React / Angular / Vue", + "emoji": true + }, + "value": "react_angular_vue" + }, + { + "text": { + "type": "plain_text", + "text": "Remote Working", + "emoji": true + }, + "value": "remote_working" + }, + { + "text": { + "type": "plain_text", + "text": "Resume Reviews", + "emoji": true + }, + "value": "resume_reviews" + }, + { + "text": { + "type": "plain_text", + "text": "Ruby / Rails", + "emoji": true + }, + "value": "ruby_rails" + }, + { + "text": { + "type": "plain_text", + "text": "Rust", + "emoji": true + }, + "value": "rust" + }, + { + "text": { + "type": "plain_text", + "text": "Salesforce", + "emoji": true + }, + "value": "salesforce" + }, + { + "text": { + "type": "plain_text", + "text": "Scala", + "emoji": true + }, + "value": "scala" + }, + { + "text": { + "type": "plain_text", + "text": "SQL", + "emoji": true + }, + "value": "sql" + }, + { + "text": { + "type": "plain_text", + "text": "Study Help", + "emoji": true + }, + "value": "study_help" + }, + { + "text": { + "type": "plain_text", + "text": "Transition Assistance", + "emoji": true + }, + "value": "transition_assistance" + }, + { + "text": { + "type": "plain_text", + "text": "VA Benefits / Financial Aid", + "emoji": true + }, + "value": "va_benefits_financial_aid" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Back-end)", + "emoji": true + }, + "value": "web_development_back_end" + }, + { + "text": { + "type": "plain_text", + "text": "Web Development (Front-end)", + "emoji": true + }, + "value": "web_development_front_end" + }, + { + "text": { + "type": "plain_text", + "text": "Xamarin", + "emoji": true + }, + "value": "xamarin" + } + ] + } + }, + { + "type": "input", + "block_id": "details_input_block", + "label": { + "type": "plain_text", + "text": "Request Details*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "plain_text_input", + "action_id": "details_text_input", + "placeholder": { + "type": "plain_text", + "text": "Please enter more information about your request. The more specific you are the easier it is to match you with a great mentor!", + "emoji": true + }, + "multiline": true, + "min_length": 10, + "dispatch_action_config": { + "trigger_actions_on": [ + "on_enter_pressed" + ] + } + } + }, + { + "type": "divider", + "block_id": "mentorship_request_divider_2" + }, + { + "type": "input", + "block_id": "mentorship_affiliation_input", + "label": { + "type": "plain_text", + "text": "I certify that I am affiliated with one of the following groups:*", + "emoji": true + }, + "optional": false, + "dispatch_action": false, + "element": { + "type": "static_select", + "action_id": "mentorship_affiliation_selection", + "placeholder": { + "type": "plain_text", + "text": "Please select a group...", + "emoji": true + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "US Veteran", + "emoji": true + }, + "value": "us_veteran" + }, + { + "text": { + "type": "plain_text", + "text": "US Military Spouse", + "emoji": true + }, + "value": "us_military_spouse" + }, + { + "text": { + "type": "plain_text", + "text": "US Active Duty", + "emoji": true + }, + "value": "us_active_duty" + }, + { + "text": { + "type": "plain_text", + "text": "Non-Military", + "emoji": true + }, + "value": "non_military" + }, + { + "text": { + "type": "plain_text", + "text": "Foreign Military", + "emoji": true + }, + "value": "foreign_military" + } + ] + } + } + ], + "private_metadata": "", + "callback_id": "mentorship_request_form_submit", + "state": { + "values": {} + }, + "hash": "1640917120.htsuStxA", + "title": { + "type": "plain_text", + "text": "OC Mentor Request", + "emoji": true + }, + "clear_on_close": false, + "notify_on_close": false, + "close": "None", + "submit": { + "type": "plain_text", + "text": "Submit Request", + "emoji": true + }, + "previous_view_id": "None", + "root_view_id": "V02S57SESLW", + "app_id": "A02R6C6S9JN", + "external_id": "mentorship_request_form_modal", + "app_installed_team_id": "T01SBLCQ57A", + "bot_id": "B02QRQ4KU5V" + } +} diff --git a/modules/utils/message_scheduler.py b/modules/utils/message_scheduler.py new file mode 100644 index 00000000..3cdddf27 --- /dev/null +++ b/modules/utils/message_scheduler.py @@ -0,0 +1,90 @@ +"""Module for scheduling messages in a background job.""" +import logging +from datetime import datetime, timedelta, timezone + +from slack_bolt.async_app import AsyncApp +from starlette import status + +from modules.airtable import scheduled_message_table +from modules.slack.blocks.announcement_blocks import general_announcement_blocks +from modules.utils import slack_team + +logger = logging.getLogger(__name__) + +TOTAL_MONTHS_IN_YEAR = 12 + + +async def schedule_messages(async_app: AsyncApp) -> None: + """Schedule messages to be sent to various channels. + + Pulls the messages from the Airtable table Scheduled Messages + As explained in the comments below, will schedule messages using the `when_to-send` field on the table, + which is calculated by Airtable based on the frequency and `scheduled_next` datetime. + + :param async_app: the Slack Bolt async application. + """ + logger.info("STAGE: Beginning task schedule_messages...") + messages = scheduled_message_table.all_valid_scheduled_messages + logger.debug( + "Retrieved valid messages to be potentially be scheduled", + extra={"number_of_message": len(messages)}, + ) + for message in messages: + # If we had scheduled this message to be sent at a time in the past, proceed + if message.scheduled_next < datetime.now(tz=timezone.utc): + logger.debug("Scheduling message", extra={"message_name": message.name}) + # If when to send is in the past as well, that means we should send it immediately + if message.when_to_send < datetime.now(tz=timezone.utc): + logger.debug("Scheduling message to be sent immediately", extra={"message_name": message.name}) + send_message_timestamp = int(datetime.now(timezone.utc).timestamp()) + 240 + if message.frequency == "daily": + new_scheduled_next = datetime.now(timezone.utc) + timedelta(days=1) + elif message.frequency == "weekly": + new_scheduled_next = datetime.now(timezone.utc) + timedelta(days=7) + else: + when_to_send_month = ( + message.when_to_send.month + 1 if message.when_to_send.month < TOTAL_MONTHS_IN_YEAR else 1 + ) + when_to_send_year = ( + message.when_to_send.year + 1 + if message.when_to_send.month == TOTAL_MONTHS_IN_YEAR + else message.when_to_send.year + ) + # Should find the next Monday in the month - will have to increase the variability in frequency + # to post theses on different days + next_month = datetime(when_to_send_year, when_to_send_month, 7, tzinfo=timezone.utc) + offset = -next_month.weekday() + new_scheduled_next = next_month + timedelta(days=offset) + # Otherwise, we send it out normally using the when_to_send field + else: + send_message_timestamp = int(message.when_to_send.timestamp()) + new_scheduled_next = message.when_to_send + + channel_to_send_to = slack_team.find_channel_by_name(message.channel) + + response = await async_app.client.chat_scheduleMessage( + channel=channel_to_send_to.id, + post_at=send_message_timestamp, + text=f"Announcement in {message.channel}...", + blocks=general_announcement_blocks(message.name, message.message_text), + ) + if response.status_code == status.HTTP_200_OK: + logger.debug( + "Updating the Airtable table for row with new scheduled next time", + extra={ + "table_name": scheduled_message_table.table_name, + "airtable_id": message.airtable_id, + "new_scheduled_next": new_scheduled_next, + }, + ) + scheduled_message_table.update_record( + message.airtable_id, + { + "Scheduled Next": str(new_scheduled_next), + }, + ) + else: + logger.warning( + "Issue sending the scheduled message, scheduling failed with Slack response", + extra={"response": response.__dict__, "message_name": message.name}, + ) diff --git a/modules/utils/one_off_scripts.py b/modules/utils/one_off_scripts.py new file mode 100644 index 00000000..c880f080 --- /dev/null +++ b/modules/utils/one_off_scripts.py @@ -0,0 +1,67 @@ +"""One of scripts for various purposes.""" +import json +import os +import re +import sys +from pathlib import Path + +from dotenv import load_dotenv + +from modules.airtable import daily_programmer_table + +load_dotenv() + + +def main(script_to_run: str) -> None: + """Run the script. + + :param script_to_run: Name of the script to run. + """ + if script_to_run == "process_daily_programmer_files": + process_daily_programmer_files(sys.argv[2]) + + +def process_daily_programmer_files(files_directory: str) -> None: + """Process the daily programmer files. + + Used to load the daily programmer files into the database. + + :param files_directory: The directory containing the files to process. + """ + for filename in os.listdir(files_directory): + with Path.open(Path(files_directory + "/" + filename)) as file: + message_list = json.load(file) + for message in message_list: + if message["text"]: + print(f"Parsing a new message in file: {filename}") # noqa: T201 - we use print in scripts + title = re.search(r"(={2,3}.*={2,3})", message["text"]) + if title: + name = re.search(r"(\[.*?])", message["text"]) + if name: + try: + daily_programmer_table.create_record( + { + "Name": name[0].replace("[", "").replace("]", "").replace("*", ""), + "Text": message["text"][name.span()[1] + 1 :], + "Initially Posted On": filename.split(".")[0], + "Last Posted On": filename.split(".")[0], + "Posted Count": 1, + "Initial Slack TS": message["ts"], + "Blocks": message["blocks"], + }, + ) + except KeyError: + daily_programmer_table.create_record( + { + "Name": name[0].replace("[", "").replace("]", ""), + "Text": message["text"][name.span()[1] + 1 :], + "Initially Posted On": filename.split(".")[0], + "Last Posted On": filename.split(".")[0], + "Posted Count": 1, + "Initial Slack TS": message["ts"], + }, + ) + + +if __name__ == "__main__": + main(sys.argv[1]) diff --git a/modules/utils/vector_delete_data.py b/modules/utils/vector_delete_data.py new file mode 100644 index 00000000..02bce583 --- /dev/null +++ b/modules/utils/vector_delete_data.py @@ -0,0 +1,16 @@ +"""Configuration for a vector database connection.""" +import os + +import weaviate +from dotenv import load_dotenv + +load_dotenv() + +# Weaviate configuration +auth_config = weaviate.AuthApiKey(api_key=os.getenv("WEAVIATE_API_KEY", "")) + +# Create a weaviate client +weaviate_client = weaviate.Client(os.getenv("WEAVIATE_URL", "http://localhost:8015"), auth_client_secret=auth_config) + + +weaviate_client.batch.delete_objects("TextChunk", where={"operator": "Like", "valueText": "*", "path": ["text"]}) diff --git a/modules/utils/vector_ingestion.py b/modules/utils/vector_ingestion.py new file mode 100644 index 00000000..67dbf189 --- /dev/null +++ b/modules/utils/vector_ingestion.py @@ -0,0 +1,65 @@ +"""Utilities to assist with ingesting documents and turning them into vectors.""" +import datetime +import os +import uuid + +import weaviate +from dotenv import load_dotenv +from transformers import AutoTokenizer +from unstructured.cleaners.core import clean +from unstructured.documents.elements import Text +from unstructured.partition.pdf import partition_pdf +from unstructured.staging.huggingface import stage_for_transformers + +load_dotenv() + +# Weaviate configuration +auth_config = weaviate.AuthApiKey(api_key=os.getenv("WEAVIATE_API_KEY", "")) + +# Create a weaviate client +weaviate_client = weaviate.Client(os.getenv("WEAVIATE_URL", "http://localhost:8015"), auth_client_secret=auth_config) + + +def main() -> None: + weaviate_client.batch.configure(batch_size=300) + try: + model_name = "sentence-transformers/all-mpnet-base-v2" + tokenizer = AutoTokenizer.from_pretrained(model_name) + elements = partition_pdf( + "/Users/judson/Projects/OperationCode/operationcode-pybot/data/VA-Documents/2020-CFR-Title38-Vol-1.pdf", + strategy="ocr_only", + ) + staged_elements = stage_for_transformers( + [element for element in elements if isinstance(element, Text)], + tokenizer, + ) + no_items_in_batch = 0 + batch_size = 300 + for idx, element in enumerate(staged_elements): + if isinstance(element, Text): + clean_text = clean( + element.text, + bullets=True, + extra_whitespace=True, + ) + text_chunk = { + "text": clean_text, + "cfr_title_number": 38, + "ingestion_date": str(datetime.datetime.now(tz=datetime.timezone.utc).isoformat()), + "index_number": idx, + "unique_id": str(uuid.uuid4()), + } + print("Adding object to batch. On number: " + str(idx)) + weaviate_client.batch.add_data_object(text_chunk, "TextChunk") + no_items_in_batch += 1 + if no_items_in_batch >= batch_size: + print("Sending batch...") + print("Currently at count: ", idx) + weaviate_client.batch.create_objects() + no_items_in_batch = 0 + except Exception as e: + print(e) + + +if __name__ == "__main__": + main() diff --git a/modules/utils/vector_search.py b/modules/utils/vector_search.py new file mode 100644 index 00000000..b32e2031 --- /dev/null +++ b/modules/utils/vector_search.py @@ -0,0 +1,30 @@ +import os + +import weaviate +from dotenv import load_dotenv + +load_dotenv() + +print("Starting weaviate client...") +weaviate_client = weaviate.Client(os.getenv("WEAVIATE_URL", "http://localhost:8015")) + +print("Retrieve results...") +results = ( + weaviate_client.query.get( + class_name="TextChunk", properties=["text", "ingestion_date", "index_number", "unique_id"], + ) + .with_limit(10) + .with_near_text( + { + "concepts": [ + "the VET TEC (Veteran Employment Through Technology Education Courses) program is a VA (Department of Veterans Affairs) initiative that provides funding for eligible veterans to receive training in technology-related fields. Under the VET TEC program, there is no specific maximum amount allowed for a single veteran. However, the program covers the cost of tuition and fees for eligible veterans, up to a maximum of $10,000. It's important to note that the funding provided is for the training program itself and does not cover additional expenses such as housing or books.", + ], + }, + ) + .do() +) + +results = results.get("data", {}).get("Get").get("TextChunk", {}) +print(f"Found {len(results)} results:") +for i, article in enumerate(results): + print(f"\t{i}. {article['text']}\n\n") diff --git a/poetry.lock b/poetry.lock index f706c1c6..baa2b726 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,1053 +1,3724 @@ -[[package]] -category = "main" -description = "Asyncio support for PEP-567 contextvars backport." -name = "aiocontextvars" -optional = false -python-versions = ">=3.5" -version = "0.2.2" +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] +name = "absl-py" +version = "2.0.0" +description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." category = "main" -description = "File support for asyncio." -name = "aiofiles" optional = false -python-versions = "*" -version = "0.4.0" +python-versions = ">=3.7" +files = [ + {file = "absl-py-2.0.0.tar.gz", hash = "sha256:d9690211c5fcfefcdd1a45470ac2b5c5acd45241c3af71eed96bc5441746c0d5"}, + {file = "absl_py-2.0.0-py3-none-any.whl", hash = "sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3"}, +] [[package]] -category = "main" -description = "Async http client/server framework (asyncio)" name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +category = "main" optional = false -python-versions = ">=3.5.3" -version = "3.6.2" +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] [package.dependencies] -async-timeout = ">=3.0,<4.0" +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -chardet = ">=2.0,<4.0" -multidict = ">=4.5,<5.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] +speedups = ["Brotli", "aiodns", "cchardet"] [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false -python-versions = "*" -version = "1.4.4" +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" category = "main" -description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] name = "apscheduler" +version = "3.10.4" +description = "In-process task scheduler with Cron-like capabilities" +category = "main" optional = false -python-versions = "*" -version = "3.6.3" +python-versions = ">=3.6" +files = [ + {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, + {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, +] [package.dependencies] pytz = "*" -setuptools = ">=0.7" six = ">=1.4.0" -tzlocal = ">=1.2" +tzlocal = ">=2.0,<3.0.0 || >=4.0.0" [package.extras] -asyncio = ["trollius"] doc = ["sphinx", "sphinx-rtd-theme"] gevent = ["gevent"] -mongodb = ["pymongo (>=2.8)"] +mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] -sqlalchemy = ["sqlalchemy (>=0.8)"] -testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +sqlalchemy = ["sqlalchemy (>=1.4)"] +testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] -category = "main" -description = "Timeout context manager for asyncio programs" -name = "async-timeout" +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +category = "dev" optional = false -python-versions = ">=3.5.3" -version = "3.0.1" +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} [[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" category = "main" -description = "Decorator that turns async generator functions into async context managers." -name = "asyncio-contextmanager" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] [[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" category = "main" -description = "An asyncio PosgtreSQL driver" -name = "asyncpg" optional = false -python-versions = ">=3.5.0" -version = "0.18.3" +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] [package.extras] -dev = ["Cython (0.29)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "flake8 (>=3.5.0,<3.6.0)", "uvloop (>=0.8.0)"] -docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] -test = ["flake8 (>=3.5.0,<3.6.0)", "uvloop (>=0.8.0)"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] -category = "dev" -description = "Enhance the standard unittest package with features for testing asyncio libraries" -name = "asynctest" +name = "authlib" +version = "1.2.1" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +category = "main" optional = false -python-versions = ">=3.5" -version = "0.13.0" +python-versions = "*" +files = [ + {file = "Authlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:c88984ea00149a90e3537c964327da930779afa4564e354edfd98410bea01911"}, + {file = "Authlib-1.2.1.tar.gz", hash = "sha256:421f7c6b468d907ca2d9afede256f068f87e34d23dd221c07d13d4c234726afb"}, +] -[[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" -name = "atomicwrites" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" +[package.dependencies] +cryptography = ">=3.2" [[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" category = "main" -description = "Classes Without Boilerplate" -name = "attrs" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.2.0" +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +html5lib = ["html5lib"] +lxml = ["lxml"] [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "23.9.1" +description = "The uncompromising code formatter." +category = "dev" optional = false -python-versions = ">=3.6" -version = "20.8b1" +python-versions = ">=3.8" +files = [ + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, +] [package.dependencies] -appdirs = "*" -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] +name = "cachetools" +version = "5.3.1" +description = "Extensible memoizing collections and decorators" category = "main" -description = "cChardet is high speed universal character encoding detector." -name = "cchardet" optional = false -python-versions = "*" -version = "2.1.6" +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, +] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false -python-versions = "*" -version = "2020.6.20" +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] [[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." category = "main" -description = "Universal encoding detector for Python 2 and 3" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +category = "main" optional = false -python-versions = "*" -version = "3.0.4" +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] [[package]] -category = "dev" -description = "Composable command line interface toolkit" -name = "click" +name = "charset-normalizer" +version = "3.3.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, +] [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" -name = "colorama" +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." category = "main" -description = "The Cython compiler for writing C extensions for the Python language." -name = "cython" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.29.21" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] -category = "dev" -description = "the modular source code checker: pep8 pyflakes and co" -name = "flake8" +name = "cryptography" +version = "41.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.3" +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, +] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +cffi = ">=1.12" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] [[package]] +name = "dataclasses-json" +version = "0.6.1" +description = "Easily serialize dataclasses to and from JSON." category = "main" -description = "An async GitHub API library" -name = "gidgethub" optional = false -python-versions = ">=3.6" -version = "3.3.0" +python-versions = ">=3.7,<4.0" +files = [ + {file = "dataclasses_json-0.6.1-py3-none-any.whl", hash = "sha256:1bd8418a61fe3d588bb0079214d7fb71d44937da40742b787256fd53b26b6c80"}, + {file = "dataclasses_json-0.6.1.tar.gz", hash = "sha256:a53c220c35134ce08211a1057fd0e5bf76dc5331627c6b241cacbc570a89faae"}, +] [package.dependencies] -uritemplate = ">=3.0.0" +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] [package.extras] -aiohttp = ["aiohttp"] -dev = ["aiohttp", "httpx", "mypy", "pytest-cov", "treq", "twisted", "tornado"] -doc = ["sphinx"] -httpx = ["httpx (>=0.11.0)"] -test = ["pytest (>=3.0.0)", "pytest-asyncio", "pytest-tornasync"] -tornado = ["tornado"] -treq = ["treq", "twisted"] +graph = ["objgraph (>=1.7.2)"] [[package]] +name = "emoji" +version = "2.8.0" +description = "Emoji for Python" category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" +files = [ + {file = "emoji-2.8.0-py2.py3-none-any.whl", hash = "sha256:a8468fd836b7ecb6d1eac054c9a591701ce0ccd6c6f7779ad71b66f76664df90"}, + {file = "emoji-2.8.0.tar.gz", hash = "sha256:8d8b5dec3c507444b58890e598fc895fcec022b3f5acb49497c6ccc5208b8b00"}, +] + +[package.extras] +dev = ["coverage", "coveralls", "pytest"] [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" -name = "importlib-metadata" +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "2.0.0" - -[package.dependencies] -zipp = ">=0.5" +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] [package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +test = ["pytest (>=6)"] [[package]] -category = "dev" -description = "iniconfig: brain-dead simple config-ini parsing" -name = "iniconfig" +name = "fastapi" +version = "0.103.2" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"}, + {file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"}, +] + +[package.dependencies] +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." -name = "isort" +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "4.3.21" +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] [package.extras] -pipfile = ["pipreqs", "requirementslib"] -pyproject = ["toml"] -requirements = ["pipreqs", "pip-api"] -xdg_home = ["appdirs (>=1.4.0)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" -name = "mccabe" +name = "filetype" +version = "1.2.0" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." +category = "main" optional = false python-versions = "*" -version = "0.6.1" +files = [ + {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, + {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, +] [[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" category = "main" -description = "multidict implementation" -name = "multidict" optional = false -python-versions = ">=3.5" -version = "4.7.6" +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -name = "mypy-extensions" +name = "fsspec" +version = "2023.9.2" +description = "File-system specification" +category = "main" optional = false -python-versions = "*" -version = "0.4.3" +python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.9.2-py3-none-any.whl", hash = "sha256:603dbc52c75b84da501b9b2ec8c11e1f61c25984c4a0dda1f129ef391fbfc9b4"}, + {file = "fsspec-2023.9.2.tar.gz", hash = "sha256:80bfb8c70cc27b2178cc62a935ecf242fc6e8c3fb801f9c571fc01b1e715ba7d"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +devel = ["pytest", "pytest-cov"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +tqdm = ["tqdm"] [[package]] -category = "dev" -description = "Core utilities for Python packages" -name = "packaging" +name = "google-auth" +version = "2.23.2" +description = "Google Authentication Library" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.23.2.tar.gz", hash = "sha256:5a9af4be520ba33651471a0264eead312521566f44631cbb621164bc30c8fd40"}, + {file = "google_auth-2.23.2-py2.py3-none-any.whl", hash = "sha256:c2e253347579d483004f17c3bd0bf92e611ef6c7ba24d41c5c59f2e7aeeaf088"}, +] [package.dependencies] -pyparsing = ">=2.0.2" -six = "*" +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" -[[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." -name = "pathspec" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" +name = "google-auth-oauthlib" +version = "1.0.0" +description = "Google Authentication Library" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" +python-versions = ">=3.6" +files = [ + {file = "google-auth-oauthlib-1.0.0.tar.gz", hash = "sha256:e375064964820b47221a7e1b7ee1fd77051b6323c3f9e3e19785f78ab67ecfc5"}, + {file = "google_auth_oauthlib-1.0.0-py2.py3-none-any.whl", hash = "sha256:95880ca704928c300f48194d1770cf5b1462835b6e49db61445a520f793fd5fb"}, +] [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +google-auth = ">=2.15.0" +requests-oauthlib = ">=0.7.0" [package.extras] -dev = ["pre-commit", "tox"] +tool = ["click (>=6.0.0)"] [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -name = "py" +name = "grpcio" +version = "1.59.0" +description = "HTTP/2-based RPC framework" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.59.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd"}, + {file = "grpcio-1.59.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0"}, + {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b"}, + {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7"}, + {file = "grpcio-1.59.0-cp310-cp310-win32.whl", hash = "sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211"}, + {file = "grpcio-1.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4"}, + {file = "grpcio-1.59.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09"}, + {file = "grpcio-1.59.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d"}, + {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9"}, + {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5"}, + {file = "grpcio-1.59.0-cp311-cp311-win32.whl", hash = "sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7"}, + {file = "grpcio-1.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29"}, + {file = "grpcio-1.59.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5"}, + {file = "grpcio-1.59.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52"}, + {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9"}, + {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10"}, + {file = "grpcio-1.59.0-cp312-cp312-win32.whl", hash = "sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205"}, + {file = "grpcio-1.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88"}, + {file = "grpcio-1.59.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c"}, + {file = "grpcio-1.59.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a"}, + {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0"}, + {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575"}, + {file = "grpcio-1.59.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897"}, + {file = "grpcio-1.59.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055"}, + {file = "grpcio-1.59.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce"}, + {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901"}, + {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1"}, + {file = "grpcio-1.59.0-cp38-cp38-win32.whl", hash = "sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4"}, + {file = "grpcio-1.59.0-cp38-cp38-win_amd64.whl", hash = "sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c"}, + {file = "grpcio-1.59.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857"}, + {file = "grpcio-1.59.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a"}, + {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df"}, + {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0"}, + {file = "grpcio-1.59.0-cp39-cp39-win32.whl", hash = "sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f"}, + {file = "grpcio-1.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81"}, + {file = "grpcio-1.59.0.tar.gz", hash = "sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f"}, +] -[[package]] -category = "dev" -description = "Python style guide checker" -name = "pycodestyle" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" +[package.extras] +protobuf = ["grpcio-tools (>=1.59.0)"] [[package]] -category = "dev" -description = "passive checker of Python programs" -name = "pyflakes" +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] [[package]] -category = "dev" -description = "Python parsing module" -name = "pyparsing" +name = "httptools" +version = "0.6.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" +python-versions = ">=3.5.0" +files = [ + {file = "httptools-0.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:818325afee467d483bfab1647a72054246d29f9053fd17cc4b86cda09cc60339"}, + {file = "httptools-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72205730bf1be875003692ca54a4a7c35fac77b4746008966061d9d41a61b0f5"}, + {file = "httptools-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33eb1d4e609c835966e969a31b1dedf5ba16b38cab356c2ce4f3e33ffa94cad3"}, + {file = "httptools-0.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdc6675ec6cb79d27e0575750ac6e2b47032742e24eed011b8db73f2da9ed40"}, + {file = "httptools-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:463c3bc5ef64b9cf091be9ac0e0556199503f6e80456b790a917774a616aff6e"}, + {file = "httptools-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f228b88b0e8c6099a9c4757ce9fdbb8b45548074f8d0b1f0fc071e35655d1c"}, + {file = "httptools-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:0781fedc610293a2716bc7fa142d4c85e6776bc59d617a807ff91246a95dea35"}, + {file = "httptools-0.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:721e503245d591527cddd0f6fd771d156c509e831caa7a57929b55ac91ee2b51"}, + {file = "httptools-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:274bf20eeb41b0956e34f6a81f84d26ed57c84dd9253f13dcb7174b27ccd8aaf"}, + {file = "httptools-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:259920bbae18740a40236807915def554132ad70af5067e562f4660b62c59b90"}, + {file = "httptools-0.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bfd2ae8a2d532952ac54445a2fb2504c804135ed28b53fefaf03d3a93eb1fd"}, + {file = "httptools-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f959e4770b3fc8ee4dbc3578fd910fab9003e093f20ac8c621452c4d62e517cb"}, + {file = "httptools-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e22896b42b95b3237eccc42278cd72c0df6f23247d886b7ded3163452481e38"}, + {file = "httptools-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:38f3cafedd6aa20ae05f81f2e616ea6f92116c8a0f8dcb79dc798df3356836e2"}, + {file = "httptools-0.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:47043a6e0ea753f006a9d0dd076a8f8c99bc0ecae86a0888448eb3076c43d717"}, + {file = "httptools-0.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a541579bed0270d1ac10245a3e71e5beeb1903b5fbbc8d8b4d4e728d48ff1d"}, + {file = "httptools-0.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d802e7b2538a9756df5acc062300c160907b02e15ed15ba035b02bce43e89c"}, + {file = "httptools-0.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:26326e0a8fe56829f3af483200d914a7cd16d8d398d14e36888b56de30bec81a"}, + {file = "httptools-0.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e41ccac9e77cd045f3e4ee0fc62cbf3d54d7d4b375431eb855561f26ee7a9ec4"}, + {file = "httptools-0.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e748fc0d5c4a629988ef50ac1aef99dfb5e8996583a73a717fc2cac4ab89932"}, + {file = "httptools-0.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cf8169e839a0d740f3d3c9c4fa630ac1a5aaf81641a34575ca6773ed7ce041a1"}, + {file = "httptools-0.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dcc14c090ab57b35908d4a4585ec5c0715439df07be2913405991dbb37e049d"}, + {file = "httptools-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0b0571806a5168013b8c3d180d9f9d6997365a4212cb18ea20df18b938aa0b"}, + {file = "httptools-0.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb4a608c631f7dcbdf986f40af7a030521a10ba6bc3d36b28c1dc9e9035a3c0"}, + {file = "httptools-0.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:93f89975465133619aea8b1952bc6fa0e6bad22a447c6d982fc338fbb4c89649"}, + {file = "httptools-0.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:73e9d66a5a28b2d5d9fbd9e197a31edd02be310186db423b28e6052472dc8201"}, + {file = "httptools-0.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:22c01fcd53648162730a71c42842f73b50f989daae36534c818b3f5050b54589"}, + {file = "httptools-0.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f96d2a351b5625a9fd9133c95744e8ca06f7a4f8f0b8231e4bbaae2c485046a"}, + {file = "httptools-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72ec7c70bd9f95ef1083d14a755f321d181f046ca685b6358676737a5fecd26a"}, + {file = "httptools-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b703d15dbe082cc23266bf5d9448e764c7cb3fcfe7cb358d79d3fd8248673ef9"}, + {file = "httptools-0.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c723ed5982f8ead00f8e7605c53e55ffe47c47465d878305ebe0082b6a1755"}, + {file = "httptools-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0a816bb425c116a160fbc6f34cece097fd22ece15059d68932af686520966bd"}, + {file = "httptools-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dea66d94e5a3f68c5e9d86e0894653b87d952e624845e0b0e3ad1c733c6cc75d"}, + {file = "httptools-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:23b09537086a5a611fad5696fc8963d67c7e7f98cb329d38ee114d588b0b74cd"}, + {file = "httptools-0.6.0.tar.gz", hash = "sha256:9fc6e409ad38cbd68b177cd5158fc4042c796b82ca88d99ec78f07bed6c6b796"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" +name = "huggingface-hub" +version = "0.17.3" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +category = "main" optional = false -python-versions = ">=3.5" -version = "6.1.0" +python-versions = ">=3.8.0" +files = [ + {file = "huggingface_hub-0.17.3-py3-none-any.whl", hash = "sha256:545eb3665f6ac587add946e73984148f2ea5c7877eac2e845549730570c1933a"}, + {file = "huggingface_hub-0.17.3.tar.gz", hash = "sha256:40439632b211311f788964602bf8b0d9d6b7a2314fba4e8d67b2ce3ecea0e3fd"}, +] [package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = "*" -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.8.2" -toml = "*" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +filelock = "*" +fsspec = "*" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" [package.extras] -checkqa_mypy = ["mypy (0.780)"] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "black (==23.7)", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (<2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.0.241)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "black (==23.7)", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (<2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.0.241)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "urllib3 (<2.0)"] +docs = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "black (==23.7)", "gradio", "hf-doc-builder", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (<2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.0.241)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "urllib3 (<2.0)", "watchdog"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +inference = ["aiohttp", "pydantic (<2.0)"] +quality = ["black (==23.7)", "mypy (==1.5.1)", "ruff (>=0.0.241)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic (<2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["torch"] +typing = ["pydantic (<2.0)", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3"] [[package]] -category = "dev" -description = "pytest plugin for aiohttp support" -name = "pytest-aiohttp" +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false -python-versions = "*" -version = "0.3.0" - -[package.dependencies] -aiohttp = ">=2.3.5" -pytest = "*" +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" -description = "Pytest support for asyncio." -name = "pytest-asyncio" optional = false -python-versions = ">= 3.5" -version = "0.14.0" +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] -[package.dependencies] -pytest = ">=5.4.0" +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] [package.extras] -testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] -category = "dev" -description = "Thin-wrapper around the mock package for easier use with pytest" -name = "pytest-mock" +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" optional = false -python-versions = ">=3.5" -version = "3.3.1" +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] -pytest = ">=5.0" +MarkupSafe = ">=2.0" [package.extras] -dev = ["pre-commit", "tox", "pytest-asyncio"] +i18n = ["Babel (>=2.7)"] [[package]] +name = "joblib" +version = "1.3.2" +description = "Lightweight pipelining with Python functions" category = "main" -description = "Add .env support to your django/flask apps in development and deployments" -name = "python-dotenv" optional = false -python-versions = "*" -version = "0.14.0" +python-versions = ">=3.7" +files = [ + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, +] -[package.extras] +[[package]] +name = "langdetect" +version = "1.0.9" +description = "Language detection library ported from Google's language-detection." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, + {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + +[[package]] +name = "markdown" +version = "3.4.4" +description = "Python implementation of John Gruber's Markdown." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, +] + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "marshmallow" +version = "3.20.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy" +version = "1.5.1" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "networkx" +version = "3.1" +description = "Python package for creating and manipulating graphs and networks" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, + {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, +] + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "nltk" +version = "3.8.1" +description = "Natural Language Toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nltk-3.8.1-py3-none-any.whl", hash = "sha256:fd5c9109f976fa86bcadba8f91e47f5e9293bd034474752e92a520f81c93dda5"}, + {file = "nltk-3.8.1.zip", hash = "sha256:1834da3d0682cba4f2cede2f9aad6b0fafb6461ba451db0efb6f9c39798d64d3"}, +] + +[package.dependencies] +click = "*" +joblib = "*" +regex = ">=2021.8.3" +tqdm = "*" + +[package.extras] +all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] +corenlp = ["requests"] +machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] +plot = ["matplotlib"] +tgrep = ["pyparsing"] +twitter = ["twython"] + +[[package]] +name = "numpy" +version = "1.26.0" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd"}, + {file = "numpy-1.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292"}, + {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68"}, + {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be"}, + {file = "numpy-1.26.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:436c8e9a4bdeeee84e3e59614d38c3dbd3235838a877af8c211cfcac8a80b8d3"}, + {file = "numpy-1.26.0-cp310-cp310-win32.whl", hash = "sha256:c2e698cb0c6dda9372ea98a0344245ee65bdc1c9dd939cceed6bb91256837896"}, + {file = "numpy-1.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91"}, + {file = "numpy-1.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a"}, + {file = "numpy-1.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd"}, + {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208"}, + {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c"}, + {file = "numpy-1.26.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:546b7dd7e22f3c6861463bebb000646fa730e55df5ee4a0224408b5694cc6148"}, + {file = "numpy-1.26.0-cp311-cp311-win32.whl", hash = "sha256:c0b45c8b65b79337dee5134d038346d30e109e9e2e9d43464a2970e5c0e93229"}, + {file = "numpy-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99"}, + {file = "numpy-1.26.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388"}, + {file = "numpy-1.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581"}, + {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb"}, + {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505"}, + {file = "numpy-1.26.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4acc65dd65da28060e206c8f27a573455ed724e6179941edb19f97e58161bb69"}, + {file = "numpy-1.26.0-cp312-cp312-win32.whl", hash = "sha256:bb0d9a1aaf5f1cb7967320e80690a1d7ff69f1d47ebc5a9bea013e3a21faec95"}, + {file = "numpy-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112"}, + {file = "numpy-1.26.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2"}, + {file = "numpy-1.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8"}, + {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f"}, + {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c"}, + {file = "numpy-1.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b44e6a09afc12952a7d2a58ca0a2429ee0d49a4f89d83a0a11052da696440e49"}, + {file = "numpy-1.26.0-cp39-cp39-win32.whl", hash = "sha256:5671338034b820c8d58c81ad1dafc0ed5a00771a82fccc71d6438df00302094b"}, + {file = "numpy-1.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0792824ce2f7ea0c82ed2e4fecc29bb86bee0567a080dacaf2e0a01fe7654369"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d484292eaeb3e84a51432a94f53578689ffdea3f90e10c8b203a99be5af57d8"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:186ba67fad3c60dbe8a3abff3b67a91351100f2661c8e2a80364ae6279720299"}, + {file = "numpy-1.26.0.tar.gz", hash = "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "pdf2image" +version = "1.16.3" +description = "A wrapper around the pdftoppm and pdftocairo command line tools to convert PDF to a PIL Image list." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pdf2image-1.16.3-py3-none-any.whl", hash = "sha256:b6154164af3677211c22cbb38b2bd778b43aca02758e962fe1e231f6d3b0e380"}, + {file = "pdf2image-1.16.3.tar.gz", hash = "sha256:74208810c2cef4d9e347769b8e62a52303982ddb4f2dfd744c7ab4b940ae287e"}, +] + +[package.dependencies] +pillow = "*" + +[[package]] +name = "pillow" +version = "10.0.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.24.3" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.3-cp310-abi3-win32.whl", hash = "sha256:20651f11b6adc70c0f29efbe8f4a94a74caf61b6200472a9aea6e19898f9fcf4"}, + {file = "protobuf-4.24.3-cp310-abi3-win_amd64.whl", hash = "sha256:3d42e9e4796a811478c783ef63dc85b5a104b44aaaca85d4864d5b886e4b05e3"}, + {file = "protobuf-4.24.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675"}, + {file = "protobuf-4.24.3-cp37-cp37m-win32.whl", hash = "sha256:1b182c7181a2891e8f7f3a1b5242e4ec54d1f42582485a896e4de81aa17540c2"}, + {file = "protobuf-4.24.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b0271a701e6782880d65a308ba42bc43874dabd1a0a0f41f72d2dac3b57f8e76"}, + {file = "protobuf-4.24.3-cp38-cp38-win32.whl", hash = "sha256:e29d79c913f17a60cf17c626f1041e5288e9885c8579832580209de8b75f2a52"}, + {file = "protobuf-4.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:067f750169bc644da2e1ef18c785e85071b7c296f14ac53e0900e605da588719"}, + {file = "protobuf-4.24.3-cp39-cp39-win32.whl", hash = "sha256:2da777d34b4f4f7613cdf85c70eb9a90b1fbef9d36ae4a0ccfe014b0b07906f1"}, + {file = "protobuf-4.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:f631bb982c5478e0c1c70eab383af74a84be66945ebf5dd6b06fc90079668d0b"}, + {file = "protobuf-4.24.3-py3-none-any.whl", hash = "sha256:f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a"}, + {file = "protobuf-4.24.3.tar.gz", hash = "sha256:12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d"}, +] + +[[package]] +name = "pyairtable" +version = "1.5.0" +description = "Python Client for the Airtable API" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pyairtable-1.5.0-py2.py3-none-any.whl", hash = "sha256:078680e2db70e6fbd3e006ed0b4cce52ffe4e6954d63b0f5851bdc979f4d25b6"}, + {file = "pyairtable-1.5.0.tar.gz", hash = "sha256:1f02ec80d66c81cdef250a4592463e50c61bc2cb1de5939db67df3cdc5c2a03d"}, +] + +[package.dependencies] +requests = ">=2.22.0" +urllib3 = "<2" + +[[package]] +name = "pyaml" +version = "21.10.1" +description = "PyYAML-based module to produce pretty and readable YAML-serialized data" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, + {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, +] + +[package.dependencies] +PyYAML = "*" + +[[package]] +name = "pyasn1" +version = "0.5.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, + {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "2.4.2" +description = "Data validation using Python type hints" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pylint" +version = "2.17.7" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, +] + +[package.dependencies] +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.2", markers = "python_version < \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pypdf" +version = "3.16.2" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pypdf-3.16.2-py3-none-any.whl", hash = "sha256:d132953be1e2af7b115fbfd445459fdfc601a845ca12379160e1b6afaa1fef2c"}, + {file = "pypdf-3.16.2.tar.gz", hash = "sha256:6e000281fd0f4cd32e6f1e75b05af0b6c0fbbd9777fa9a8e9d86e34cda65419d"}, +] + +[package.extras] +crypto = ["PyCryptodome", "cryptography"] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"] +image = ["Pillow (>=8.0.0)"] + +[[package]] +name = "pytesseract" +version = "0.3.10" +description = "Python-tesseract is a python wrapper for Google's Tesseract-OCR" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytesseract-0.3.10-py3-none-any.whl", hash = "sha256:8f22cc98f765bf13517ead0c70effedb46c153540d25783e04014f28b55a5fc6"}, + {file = "pytesseract-0.3.10.tar.gz", hash = "sha256:f1c3a8b0f07fd01a1085d451f5b8315be6eec1d5577a6796d46dc7a62bd4120f"}, +] + +[package.dependencies] +packaging = ">=21.3" +Pillow = ">=8.0.0" + +[[package]] +name = "pytest" +version = "7.4.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-vcr" +version = "1.0.2" +description = "Plugin for managing VCR.py cassettes" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pytest-vcr-1.0.2.tar.gz", hash = "sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896"}, + {file = "pytest_vcr-1.0.2-py2.py3-none-any.whl", hash = "sha256:2f316e0539399bea0296e8b8401145c62b6f85e9066af7e57b6151481b0d6d9c"}, +] + +[package.dependencies] +pytest = ">=3.6.0" +vcrpy = "*" + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] cli = ["click (>=5.0)"] [[package]] +name = "python-iso639" +version = "2023.6.15" +description = "Look-up utilities for ISO 639 language codes and names" category = "main" -description = "World timezone definitions, modern and historical" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-iso639-2023.6.15.tar.gz", hash = "sha256:d456740d046d769a4263472ace1a9b790264210e0c199d61a520087c1fab7078"}, + {file = "python_iso639-2023.6.15-py3-none-any.whl", hash = "sha256:6a4e197cb4a5f39338b9cc2c6356bdfd4cd4bdf6d2a69eb8f707bc8a76f6cf9e"}, +] + +[package.extras] +dev = ["black (==23.1.0)", "build (==0.10.0)", "flake8 (==6.0.0)", "pytest (==7.2.1)", "twine (==4.0.2)"] + +[[package]] +name = "python-magic" +version = "0.4.27" +description = "File type identification using libmagic" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, + {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, +] + +[[package]] name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "regex" +version = "2023.8.8" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "ruff" +version = "0.0.291" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.291-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b97d0d7c136a85badbc7fd8397fdbb336e9409b01c07027622f28dcd7db366f2"}, + {file = "ruff-0.0.291-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6ab44ea607967171e18aa5c80335237be12f3a1523375fa0cede83c5cf77feb4"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04b384f2d36f00d5fb55313d52a7d66236531195ef08157a09c4728090f2ef0"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b727c219b43f903875b7503a76c86237a00d1a39579bb3e21ce027eec9534051"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87671e33175ae949702774071b35ed4937da06f11851af75cd087e1b5a488ac4"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b75f5801547f79b7541d72a211949754c21dc0705c70eddf7f21c88a64de8b97"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b09b94efdcd162fe32b472b2dd5bf1c969fcc15b8ff52f478b048f41d4590e09"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d5b56bc3a2f83a7a1d7f4447c54d8d3db52021f726fdd55d549ca87bca5d747"}, + {file = "ruff-0.0.291-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f0d88e5f367b2dc8c7d90a8afdcfff9dd7d174e324fd3ed8e0b5cb5dc9b7f6"}, + {file = "ruff-0.0.291-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b3eeee1b1a45a247758ecdc3ab26c307336d157aafc61edb98b825cadb153df3"}, + {file = "ruff-0.0.291-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6c06006350c3bb689765d71f810128c9cdf4a1121fd01afc655c87bab4fb4f83"}, + {file = "ruff-0.0.291-py3-none-musllinux_1_2_i686.whl", hash = "sha256:fd17220611047de247b635596e3174f3d7f2becf63bd56301fc758778df9b629"}, + {file = "ruff-0.0.291-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5383ba67ad360caf6060d09012f1fb2ab8bd605ab766d10ca4427a28ab106e0b"}, + {file = "ruff-0.0.291-py3-none-win32.whl", hash = "sha256:1d5f0616ae4cdc7a938b493b6a1a71c8a47d0300c0d65f6e41c281c2f7490ad3"}, + {file = "ruff-0.0.291-py3-none-win_amd64.whl", hash = "sha256:8a69bfbde72db8ca1c43ee3570f59daad155196c3fbe357047cd9b77de65f15b"}, + {file = "ruff-0.0.291-py3-none-win_arm64.whl", hash = "sha256:d867384a4615b7f30b223a849b52104214442b5ba79b473d7edd18da3cde22d6"}, + {file = "ruff-0.0.291.tar.gz", hash = "sha256:c61109661dde9db73469d14a82b42a88c7164f731e6a3b0042e71394c1c7ceed"}, +] + +[[package]] +name = "safetensors" +version = "0.3.3" +description = "Fast and Safe Tensor serialization" +category = "main" optional = false python-versions = "*" -version = "2020.1" +files = [ + {file = "safetensors-0.3.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:92e4d0c8b2836120fddd134474c5bda8963f322333941f8b9f643e5b24f041eb"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3dcadb6153c42addc9c625a622ebde9293fabe1973f9ef31ba10fb42c16e8536"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:08f26b61e1b0a14dc959aa9d568776bd038805f611caef1de04a80c468d4a7a4"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:17f41344d9a075f2f21b289a49a62e98baff54b5754240ba896063bce31626bf"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:f1045f798e1a16a6ced98d6a42ec72936d367a2eec81dc5fade6ed54638cd7d2"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:eaf0e4bc91da13f21ac846a39429eb3f3b7ed06295a32321fa3eb1a59b5c70f3"}, + {file = "safetensors-0.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25149180d4dc8ca48bac2ac3852a9424b466e36336a39659b35b21b2116f96fc"}, + {file = "safetensors-0.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9e943bf78c39de8865398a71818315e7d5d1af93c7b30d4da3fc852e62ad9bc"}, + {file = "safetensors-0.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cccfcac04a010354e87c7a2fe16a1ff004fc4f6e7ef8efc966ed30122ce00bc7"}, + {file = "safetensors-0.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a07121f427e646a50d18c1be0fa1a2cbf6398624c31149cd7e6b35486d72189e"}, + {file = "safetensors-0.3.3-cp310-cp310-win32.whl", hash = "sha256:a85e29cbfddfea86453cc0f4889b4bcc6b9c155be9a60e27be479a34e199e7ef"}, + {file = "safetensors-0.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:e13adad4a3e591378f71068d14e92343e626cf698ff805f61cdb946e684a218e"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cbc3312f134baf07334dd517341a4b470b2931f090bd9284888acb7dfaf4606f"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d15030af39d5d30c22bcbc6d180c65405b7ea4c05b7bab14a570eac7d7d43722"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:f84a74cbe9859b28e3d6d7715ac1dd3097bebf8d772694098f6d42435245860c"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:10d637423d98ab2e6a4ad96abf4534eb26fcaf8ca3115623e64c00759374e90d"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:3b46f5de8b44084aff2e480874c550c399c730c84b2e8ad1bddb062c94aa14e9"}, + {file = "safetensors-0.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76da691a82dfaf752854fa6d17c8eba0c8466370c5ad8cf1bfdf832d3c7ee17"}, + {file = "safetensors-0.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4e342fd54e66aa9512dd13e410f791e47aa4feeb5f4c9a20882c72f3d272f29"}, + {file = "safetensors-0.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:178fd30b5dc73bce14a39187d948cedd0e5698e2f055b7ea16b5a96c9b17438e"}, + {file = "safetensors-0.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e8fdf7407dba44587ed5e79d5de3533d242648e1f2041760b21474bd5ea5c8c"}, + {file = "safetensors-0.3.3-cp311-cp311-win32.whl", hash = "sha256:7d3b744cee8d7a46ffa68db1a2ff1a1a432488e3f7a5a97856fe69e22139d50c"}, + {file = "safetensors-0.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f579877d30feec9b6ba409d05fa174633a4fc095675a4a82971d831a8bb60b97"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:2fff5b19a1b462c17322998b2f4b8bce43c16fe208968174d2f3a1446284ceed"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:41adb1d39e8aad04b16879e3e0cbcb849315999fad73bc992091a01e379cb058"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_12_0_x86_64.whl", hash = "sha256:0f2b404250b3b877b11d34afcc30d80e7035714a1116a3df56acaca6b6c00096"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:b43956ef20e9f4f2e648818a9e7b3499edd6b753a0f5526d4f6a6826fbee8446"}, + {file = "safetensors-0.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d61a99b34169981f088ccfbb2c91170843efc869a0a0532f422db7211bf4f474"}, + {file = "safetensors-0.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0008aab36cd20e9a051a68563c6f80d40f238c2611811d7faa5a18bf3fd3984"}, + {file = "safetensors-0.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93d54166072b143084fdcd214a080a088050c1bb1651016b55942701b31334e4"}, + {file = "safetensors-0.3.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c32ee08f61cea56a5d62bbf94af95df6040c8ab574afffaeb7b44ae5da1e9e3"}, + {file = "safetensors-0.3.3-cp37-cp37m-win32.whl", hash = "sha256:351600f367badd59f7bfe86d317bb768dd8c59c1561c6fac43cafbd9c1af7827"}, + {file = "safetensors-0.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:034717e297849dae1af0a7027a14b8647bd2e272c24106dced64d83e10d468d1"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8530399666748634bc0b301a6a5523756931b0c2680d188e743d16304afe917a"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:9d741c1f1621e489ba10aa3d135b54202684f6e205df52e219d5eecd673a80c9"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:0c345fd85b4d2093a5109596ff4cd9dfc2e84992e881b4857fbc4a93a3b89ddb"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:69ccee8d05f55cdf76f7e6c87d2bdfb648c16778ef8acfd2ecc495e273e9233e"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_13_0_arm64.whl", hash = "sha256:c08a9a4b7a4ca389232fa8d097aebc20bbd4f61e477abc7065b5c18b8202dede"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:a002868d2e3f49bbe81bee2655a411c24fa1f8e68b703dec6629cb989d6ae42e"}, + {file = "safetensors-0.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bd2704cb41faa44d3ec23e8b97330346da0395aec87f8eaf9c9e2c086cdbf13"}, + {file = "safetensors-0.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2951bf3f0ad63df5e6a95263652bd6c194a6eb36fd4f2d29421cd63424c883"}, + {file = "safetensors-0.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07114cec116253ca2e7230fdea30acf76828f21614afd596d7b5438a2f719bd8"}, + {file = "safetensors-0.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab43aeeb9eadbb6b460df3568a662e6f1911ecc39387f8752afcb6a7d96c087"}, + {file = "safetensors-0.3.3-cp38-cp38-win32.whl", hash = "sha256:f2f59fce31dd3429daca7269a6b06f65e6547a0c248f5116976c3f1e9b73f251"}, + {file = "safetensors-0.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:c31ca0d8610f57799925bf08616856b39518ab772c65093ef1516762e796fde4"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:59a596b3225c96d59af412385981f17dd95314e3fffdf359c7e3f5bb97730a19"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:82a16e92210a6221edd75ab17acdd468dd958ef5023d9c6c1289606cc30d1479"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:98a929e763a581f516373ef31983ed1257d2d0da912a8e05d5cd12e9e441c93a"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:12b83f1986cd16ea0454c636c37b11e819d60dd952c26978310a0835133480b7"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:f439175c827c2f1bbd54df42789c5204a10983a30bc4242bc7deaf854a24f3f0"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:0085be33b8cbcb13079b3a8e131656e05b0bc5e6970530d4c24150f7afd76d70"}, + {file = "safetensors-0.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3ec70c87b1e910769034206ad5efc051069b105aac1687f6edcd02526767f4"}, + {file = "safetensors-0.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f490132383e5e490e710608f4acffcb98ed37f91b885c7217d3f9f10aaff9048"}, + {file = "safetensors-0.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79d1b6c7ed5596baf79c80fbce5198c3cdcc521ae6a157699f427aba1a90082d"}, + {file = "safetensors-0.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad3cc8006e7a86ee7c88bd2813ec59cd7cc75b03e6fa4af89b9c7b235b438d68"}, + {file = "safetensors-0.3.3-cp39-cp39-win32.whl", hash = "sha256:ab29f54c6b8c301ca05fa014728996bd83aac6e21528f893aaf8945c71f42b6d"}, + {file = "safetensors-0.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:0fa82004eae1a71e2aa29843ef99de9350e459a0fc2f65fc6ee0da9690933d2d"}, + {file = "safetensors-0.3.3.tar.gz", hash = "sha256:edb7072d788c4f929d0f5735d3a2fb51e5a27f833587828583b7f5747af1a2b8"}, +] + +[package.extras] +all = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] +dev = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)"] +numpy = ["numpy (>=1.21.6)"] +paddlepaddle = ["numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)"] +pinned-tf = ["tensorflow (==2.11.0)"] +quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] +tensorflow = ["numpy (>=1.21.6)", "tensorflow (>=2.11.0)"] +testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "numpy (>=1.21.6)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)"] +torch = ["numpy (>=1.21.6)", "torch (>=1.10)"] [[package]] +name = "scikit-learn" +version = "1.3.1" +description = "A set of python modules for machine learning and data mining" category = "main" -description = "YAML parser and emitter for Python" -name = "pyyaml" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" +python-versions = ">=3.8" +files = [ + {file = "scikit-learn-1.3.1.tar.gz", hash = "sha256:1a231cced3ee3fa04756b4a7ab532dc9417acd581a330adff5f2c01ac2831fcf"}, + {file = "scikit_learn-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3153612ff8d36fa4e35ef8b897167119213698ea78f3fd130b4068e6f8d2da5a"}, + {file = "scikit_learn-1.3.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6bb9490fdb8e7e00f1354621689187bef3cab289c9b869688f805bf724434755"}, + {file = "scikit_learn-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7135a03af71138669f19bc96e7d0cc8081aed4b3565cc3b131135d65fc642ba"}, + {file = "scikit_learn-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d8dee8c1f40eeba49a85fe378bdf70a07bb64aba1a08fda1e0f48d27edfc3e6"}, + {file = "scikit_learn-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4d379f2b34096105a96bd857b88601dffe7389bd55750f6f29aaa37bc6272eb5"}, + {file = "scikit_learn-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14e8775eba072ab10866a7e0596bc9906873e22c4c370a651223372eb62de180"}, + {file = "scikit_learn-1.3.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:58b0c2490eff8355dc26e884487bf8edaccf2ba48d09b194fb2f3a026dd64f9d"}, + {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f66eddfda9d45dd6cadcd706b65669ce1df84b8549875691b1f403730bdef217"}, + {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6448c37741145b241eeac617028ba6ec2119e1339b1385c9720dae31367f2be"}, + {file = "scikit_learn-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c413c2c850241998168bbb3bd1bb59ff03b1195a53864f0b80ab092071af6028"}, + {file = "scikit_learn-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:52b77cc08bd555969ec5150788ed50276f5ef83abb72e6f469c5b91a0009bbca"}, + {file = "scikit_learn-1.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a683394bc3f80b7c312c27f9b14ebea7766b1f0a34faf1a2e9158d80e860ec26"}, + {file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15d964d9eb181c79c190d3dbc2fff7338786bf017e9039571418a1d53dab236"}, + {file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ce9233cdf0cdcf0858a5849d306490bf6de71fa7603a3835124e386e62f2311"}, + {file = "scikit_learn-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:1ec668ce003a5b3d12d020d2cde0abd64b262ac5f098b5c84cf9657deb9996a8"}, + {file = "scikit_learn-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccbbedae99325628c1d1cbe3916b7ef58a1ce949672d8d39c8b190e10219fd32"}, + {file = "scikit_learn-1.3.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:845f81c7ceb4ea6bac64ab1c9f2ce8bef0a84d0f21f3bece2126adcc213dfecd"}, + {file = "scikit_learn-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8454d57a22d856f1fbf3091bd86f9ebd4bff89088819886dc0c72f47a6c30652"}, + {file = "scikit_learn-1.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d993fb70a1d78c9798b8f2f28705bfbfcd546b661f9e2e67aa85f81052b9c53"}, + {file = "scikit_learn-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:66f7bb1fec37d65f4ef85953e1df5d3c98a0f0141d394dcdaead5a6de9170347"}, +] -[[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." -name = "regex" -optional = false -python-versions = "*" -version = "2020.9.27" +[package.dependencies] +joblib = ">=1.1.1" +numpy = ">=1.17.3,<2.0" +scipy = ">=1.5.0" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.10.1)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] +tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.16.2)"] [[package]] -category = "dev" -description = "Python HTTP for Humans." -name = "requests" +name = "scipy" +version = "1.11.3" +description = "Fundamental algorithms for scientific computing in Python" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" +python-versions = "<3.13,>=3.9" +files = [ + {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"}, + {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"}, + {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"}, + {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"}, + {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"}, + {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"}, + {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"}, + {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"}, + {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"}, + {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"}, + {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"}, + {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"}, + {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"}, + {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"}, + {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"}, + {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"}, + {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"}, + {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"}, + {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"}, + {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"}, + {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"}, + {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"}, + {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"}, + {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"}, + {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"}, +] [package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +numpy = ">=1.21.6,<1.28.0" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] +name = "sentence-transformers" +version = "2.2.2" +description = "Multilingual text embeddings" category = "main" -description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "sentence-transformers-2.2.2.tar.gz", hash = "sha256:dbc60163b27de21076c9a30d24b5b7b6fa05141d68cf2553fa9a77bf79a29136"}, +] + +[package.dependencies] +huggingface-hub = ">=0.4.0" +nltk = "*" +numpy = "*" +scikit-learn = "*" +scipy = "*" +sentencepiece = "*" +torch = ">=1.6.0" +torchvision = "*" +tqdm = "*" +transformers = ">=4.6.0,<5.0.0" + +[[package]] +name = "sentencepiece" +version = "0.1.99" +description = "SentencePiece python wrapper" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "sentencepiece-0.1.99-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0eb528e70571b7c02723e5804322469b82fe7ea418c96051d0286c0fa028db73"}, + {file = "sentencepiece-0.1.99-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d7fafb2c4e4659cbdf303929503f37a26eabc4ff31d3a79bf1c5a1b338caa7"}, + {file = "sentencepiece-0.1.99-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be9cf5b9e404c245aeb3d3723c737ba7a8f5d4ba262ef233a431fa6c45f732a0"}, + {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baed1a26464998f9710d20e52607c29ffd4293e7c71c6a1f83f51ad0911ec12c"}, + {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9832f08bb372d4c8b567612f8eab9e36e268dff645f1c28f9f8e851be705f6d1"}, + {file = "sentencepiece-0.1.99-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:019e7535108e309dae2b253a75834fc3128240aa87c00eb80732078cdc182588"}, + {file = "sentencepiece-0.1.99-cp310-cp310-win32.whl", hash = "sha256:fa16a830416bb823fa2a52cbdd474d1f7f3bba527fd2304fb4b140dad31bb9bc"}, + {file = "sentencepiece-0.1.99-cp310-cp310-win_amd64.whl", hash = "sha256:14b0eccb7b641d4591c3e12ae44cab537d68352e4d3b6424944f0c447d2348d5"}, + {file = "sentencepiece-0.1.99-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6d3c56f24183a1e8bd61043ff2c58dfecdc68a5dd8955dc13bab83afd5f76b81"}, + {file = "sentencepiece-0.1.99-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed6ea1819fd612c989999e44a51bf556d0ef6abfb553080b9be3d347e18bcfb7"}, + {file = "sentencepiece-0.1.99-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2a0260cd1fb7bd8b4d4f39dc2444a8d5fd4e0a0c4d5c899810ef1abf99b2d45"}, + {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a1abff4d1ff81c77cac3cc6fefa34fa4b8b371e5ee51cb7e8d1ebc996d05983"}, + {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:004e6a621d4bc88978eecb6ea7959264239a17b70f2cbc348033d8195c9808ec"}, + {file = "sentencepiece-0.1.99-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db361e03342c41680afae5807590bc88aa0e17cfd1a42696a160e4005fcda03b"}, + {file = "sentencepiece-0.1.99-cp311-cp311-win32.whl", hash = "sha256:2d95e19168875b70df62916eb55428a0cbcb834ac51d5a7e664eda74def9e1e0"}, + {file = "sentencepiece-0.1.99-cp311-cp311-win_amd64.whl", hash = "sha256:f90d73a6f81248a909f55d8e6ef56fec32d559e1e9af045f0b0322637cb8e5c7"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:62e24c81e74bd87a6e0d63c51beb6527e4c0add67e1a17bac18bcd2076afcfeb"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57efcc2d51caff20d9573567d9fd3f854d9efe613ed58a439c78c9f93101384a"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a904c46197993bd1e95b93a6e373dca2f170379d64441041e2e628ad4afb16f"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89adf59854741c0d465f0e1525b388c0d174f611cc04af54153c5c4f36088c4"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-win32.whl", hash = "sha256:47c378146928690d1bc106fdf0da768cebd03b65dd8405aa3dd88f9c81e35dba"}, + {file = "sentencepiece-0.1.99-cp36-cp36m-win_amd64.whl", hash = "sha256:9ba142e7a90dd6d823c44f9870abdad45e6c63958eb60fe44cca6828d3b69da2"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7b1a9ae4d7c6f1f867e63370cca25cc17b6f4886729595b885ee07a58d3cec3"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0f644c9d4d35c096a538507b2163e6191512460035bf51358794a78515b74f7"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8843d23a0f686d85e569bd6dcd0dd0e0cbc03731e63497ca6d5bacd18df8b85"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6f690a1caebb4867a2e367afa1918ad35be257ecdb3455d2bbd787936f155"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-win32.whl", hash = "sha256:8a321866c2f85da7beac74a824b4ad6ddc2a4c9bccd9382529506d48f744a12c"}, + {file = "sentencepiece-0.1.99-cp37-cp37m-win_amd64.whl", hash = "sha256:c42f753bcfb7661c122a15b20be7f684b61fc8592c89c870adf52382ea72262d"}, + {file = "sentencepiece-0.1.99-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b476406da69c70586f0bb682fcca4c9b40e5059814f2db92303ea4585c650c"}, + {file = "sentencepiece-0.1.99-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfbcfe13c69d3f87b7fcd5da168df7290a6d006329be71f90ba4f56bc77f8561"}, + {file = "sentencepiece-0.1.99-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:445b0ec381af1cd4eef95243e7180c63d9c384443c16c4c47a28196bd1cda937"}, + {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6890ea0f2b4703f62d0bf27932e35808b1f679bdb05c7eeb3812b935ba02001"}, + {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb71af492b0eefbf9f2501bec97bcd043b6812ab000d119eaf4bd33f9e283d03"}, + {file = "sentencepiece-0.1.99-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b866b5bd3ddd54166bbcbf5c8d7dd2e0b397fac8537991c7f544220b1f67bc"}, + {file = "sentencepiece-0.1.99-cp38-cp38-win32.whl", hash = "sha256:b133e8a499eac49c581c3c76e9bdd08c338cc1939e441fee6f92c0ccb5f1f8be"}, + {file = "sentencepiece-0.1.99-cp38-cp38-win_amd64.whl", hash = "sha256:0eaf3591dd0690a87f44f4df129cf8d05d8a4029b5b6709b489b8e27f9a9bcff"}, + {file = "sentencepiece-0.1.99-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38efeda9bbfb55052d482a009c6a37e52f42ebffcea9d3a98a61de7aee356a28"}, + {file = "sentencepiece-0.1.99-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6c030b081dc1e1bcc9fadc314b19b740715d3d566ad73a482da20d7d46fd444c"}, + {file = "sentencepiece-0.1.99-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84dbe53e02e4f8a2e45d2ac3e430d5c83182142658e25edd76539b7648928727"}, + {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b0f55d0a0ee1719b4b04221fe0c9f0c3461dc3dabd77a035fa2f4788eb3ef9a"}, + {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e800f206cd235dc27dc749299e05853a4e4332e8d3dfd81bf13d0e5b9007d9"}, + {file = "sentencepiece-0.1.99-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae1c40cda8f9d5b0423cfa98542735c0235e7597d79caf318855cdf971b2280"}, + {file = "sentencepiece-0.1.99-cp39-cp39-win32.whl", hash = "sha256:c84ce33af12ca222d14a1cdd37bd76a69401e32bc68fe61c67ef6b59402f4ab8"}, + {file = "sentencepiece-0.1.99-cp39-cp39-win_amd64.whl", hash = "sha256:350e5c74d739973f1c9643edb80f7cc904dc948578bcb1d43c6f2b173e5d18dd"}, + {file = "sentencepiece-0.1.99.tar.gz", hash = "sha256:189c48f5cb2949288f97ccdb97f0473098d9c3dcf5a3d99d4eabe719ec27297f"}, +] + +[[package]] name = "sentry-sdk" +version = "1.31.0" +description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" -version = "0.17.8" +files = [ + {file = "sentry-sdk-1.31.0.tar.gz", hash = "sha256:6de2e88304873484207fed836388e422aeff000609b104c802749fd89d56ba5b"}, + {file = "sentry_sdk-1.31.0-py2.py3-none-any.whl", hash = "sha256:64a7141005fb775b9db298a30de93e3b83e0ddd1232dc6f36eb38aebc1553291"}, +] [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] -pure_eval = ["pure-eval", "executing", "asttokens"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] [[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" -description = "The good Sir Bot-a-lot. An asynchronous python bot framework." -name = "sirbot" optional = false -python-versions = ">=3.6,<4.0" -version = "0.1.1" +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] -[package.dependencies] -aiofiles = ">=0.4.0,<0.5.0" -aiohttp = ">=3.4,<4.0" -apscheduler = ">=3.5,<4.0" -asyncio-contextmanager = ">=1.0,<2.0" -asyncpg = ">=0.18.2,<0.19.0" -gidgethub = ">=3.0,<4.0" -slack-sansio = ">=1.0.0,<2.0.0" -ujson = ">=1.35,<2.0" +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "slack-bolt" +version = "1.18.0" +description = "The Bolt Framework for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "slack_bolt-1.18.0-py2.py3-none-any.whl", hash = "sha256:63089a401ae3900c37698890249acd008a4651d06e86194edc7b72a00819bbac"}, + {file = "slack_bolt-1.18.0.tar.gz", hash = "sha256:43b121acf78440303ce5129e53be36bdfe5d926a193daef7daf2860688e65dd3"}, +] + +[package.dependencies] +slack-sdk = ">=3.21.2,<4" + +[package.extras] +adapter = ["CherryPy (>=18,<19)", "Django (>=3,<5)", "Flask (>=1,<3)", "Werkzeug (>=2,<3)", "boto3 (<=2)", "bottle (>=0.12,<1)", "chalice (>=1.28,<2)", "falcon (>=2,<4)", "fastapi (>=0.70.0,<1)", "gunicorn (>=20,<21)", "pyramid (>=1,<3)", "sanic (>=22,<23)", "starlette (>=0.14,<1)", "tornado (>=6,<7)", "uvicorn (<1)", "websocket-client (>=1.2.3,<2)"] +adapter-testing = ["Flask (>=1,<2)", "Werkzeug (>=1,<2)", "boddle (>=0.2,<0.3)", "docker (>=5,<6)", "moto (>=3,<4)", "requests (>=2,<3)", "sanic-testing (>=0.7)"] +async = ["aiohttp (>=3,<4)", "websockets (>=10,<11)"] +testing = ["Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (>=1,<2)", "aiohttp (>=3,<4)", "black (==22.8.0)", "click (<=8.0.4)", "itsdangerous (==2.0.1)", "pytest (>=6.2.5,<7)", "pytest-asyncio (>=0.18.2,<1)", "pytest-cov (>=3,<4)"] +testing-without-asyncio = ["Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (>=1,<2)", "black (==22.8.0)", "click (<=8.0.4)", "itsdangerous (==2.0.1)", "pytest (>=6.2.5,<7)", "pytest-cov (>=3,<4)"] [[package]] +name = "slack-sdk" +version = "3.22.0" +description = "The Slack API Platform SDK for Python" category = "main" -description = "Python (a)sync Slack API library" -name = "slack-sansio" optional = false -python-versions = ">=3.6,<4.0" -version = "1.1.0" +python-versions = ">=3.6.0" +files = [ + {file = "slack_sdk-3.22.0-py2.py3-none-any.whl", hash = "sha256:f102a4902115dff3b97c3e8883ad4e22d54732221886fc5ef29bfc290f063b4a"}, + {file = "slack_sdk-3.22.0.tar.gz", hash = "sha256:6eacce0fa4f8cfb4d84eac0d7d7e1b1926040a2df654ae86b94179bdf2bc4d8c"}, +] [package.extras] -aiohttp = ["aiohttp (>=3.4,<4.0)"] -curio = ["curio (>=0.9.0,<0.10.0)", "asks (>=2.2,<3.0)"] -requests = ["requests (>=2.20,<3.0)", "websocket-client (>=0.54.0,<0.55.0)"] -trio = ["asks (>=2.2,<3.0)", "trio (>=0.11.0,<0.12.0)"] +optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"] +testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.8.0)", "boto3 (<=2)", "click (==8.0.4)", "flake8 (>=5,<6)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"] [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "sympy" +version = "1.12" +description = "Computer algebra system (CAS) in Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, + {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, +] + +[package.dependencies] +mpmath = ">=0.19" + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tensorboard" +version = "2.14.1" +description = "TensorBoard lets you watch Tensors Flow" +category = "main" +optional = false +python-versions = ">=3.9" +files = [ + {file = "tensorboard-2.14.1-py3-none-any.whl", hash = "sha256:3db108fb58f023b6439880e177743c5f1e703e9eeb5fb7d597871f949f85fd58"}, +] + +[package.dependencies] +absl-py = ">=0.4" +google-auth = ">=1.6.3,<3" +google-auth-oauthlib = ">=0.5,<1.1" +grpcio = ">=1.48.2" +markdown = ">=2.6.8" +numpy = ">=1.12.0" +protobuf = ">=3.19.6" +requests = ">=2.21.0,<3" +setuptools = ">=41.0.0" +six = ">1.9" +tensorboard-data-server = ">=0.7.0,<0.8.0" +werkzeug = ">=1.0.1" + +[[package]] +name = "tensorboard-data-server" +version = "0.7.1" +description = "Fast data loading for TensorBoard" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tensorboard_data_server-0.7.1-py3-none-any.whl", hash = "sha256:9938bd39f5041797b33921066fba0eab03a0dd10d1887a05e62ae58841ad4c3f"}, + {file = "tensorboard_data_server-0.7.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:be8d016a1aa394e6198280d4a3dc37898f56467310c5f5e617cac10a783e055a"}, + {file = "tensorboard_data_server-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:255c02b7f5b03dd5c0a88c928e563441ff39e1d4b4a234cdbe09f016e53d9594"}, +] + +[[package]] +name = "threadpoolctl" +version = "3.2.0" +description = "threadpoolctl" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032"}, + {file = "threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355"}, +] + +[[package]] +name = "tokenizers" +version = "0.13.3" +description = "Fast and Customizable Tokenizers" +category = "main" optional = false python-versions = "*" -version = "0.10.1" +files = [ + {file = "tokenizers-0.13.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:f3835c5be51de8c0a092058a4d4380cb9244fb34681fd0a295fbf0a52a5fdf33"}, + {file = "tokenizers-0.13.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4ef4c3e821730f2692489e926b184321e887f34fb8a6b80b8096b966ba663d07"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5fd1a6a25353e9aa762e2aae5a1e63883cad9f4e997c447ec39d071020459bc"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee0b1b311d65beab83d7a41c56a1e46ab732a9eed4460648e8eb0bd69fc2d059"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ef4215284df1277dadbcc5e17d4882bda19f770d02348e73523f7e7d8b8d396"}, + {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d53976079cff8a033f778fb9adca2d9d69d009c02fa2d71a878b5f3963ed30"}, + {file = "tokenizers-0.13.3-cp310-cp310-win32.whl", hash = "sha256:1f0e3b4c2ea2cd13238ce43548959c118069db7579e5d40ec270ad77da5833ce"}, + {file = "tokenizers-0.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:89649c00d0d7211e8186f7a75dfa1db6996f65edce4b84821817eadcc2d3c79e"}, + {file = "tokenizers-0.13.3-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:56b726e0d2bbc9243872b0144515ba684af5b8d8cd112fb83ee1365e26ec74c8"}, + {file = "tokenizers-0.13.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc5c022ce692e1f499d745af293ab9ee6f5d92538ed2faf73f9708c89ee59ce6"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55c981ac44ba87c93e847c333e58c12abcbb377a0c2f2ef96e1a266e4184ff2"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f247eae99800ef821a91f47c5280e9e9afaeed9980fc444208d5aa6ba69ff148"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e3215d048e94f40f1c95802e45dcc37c5b05eb46280fc2ccc8cd351bff839"}, + {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba2b0bf01777c9b9bc94b53764d6684554ce98551fec496f71bc5be3a03e98b"}, + {file = "tokenizers-0.13.3-cp311-cp311-win32.whl", hash = "sha256:cc78d77f597d1c458bf0ea7c2a64b6aa06941c7a99cb135b5969b0278824d808"}, + {file = "tokenizers-0.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:ecf182bf59bd541a8876deccf0360f5ae60496fd50b58510048020751cf1724c"}, + {file = "tokenizers-0.13.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:0527dc5436a1f6bf2c0327da3145687d3bcfbeab91fed8458920093de3901b44"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cbb2c307627dc99b44b22ef05ff4473aa7c7cc1fec8f0a8b37d8a64b1a16d2"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4560dbdeaae5b7ee0d4e493027e3de6d53c991b5002d7ff95083c99e11dd5ac0"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64064bd0322405c9374305ab9b4c07152a1474370327499911937fd4a76d004b"}, + {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8c6e2ab0f2e3d939ca66aa1d596602105fe33b505cd2854a4c1717f704c51de"}, + {file = "tokenizers-0.13.3-cp37-cp37m-win32.whl", hash = "sha256:6cc29d410768f960db8677221e497226e545eaaea01aa3613fa0fdf2cc96cff4"}, + {file = "tokenizers-0.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fc2a7fdf864554a0dacf09d32e17c0caa9afe72baf9dd7ddedc61973bae352d8"}, + {file = "tokenizers-0.13.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8791dedba834c1fc55e5f1521be325ea3dafb381964be20684b92fdac95d79b7"}, + {file = "tokenizers-0.13.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:d607a6a13718aeb20507bdf2b96162ead5145bbbfa26788d6b833f98b31b26e1"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3791338f809cd1bf8e4fee6b540b36822434d0c6c6bc47162448deee3f77d425"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2f35f30e39e6aab8716f07790f646bdc6e4a853816cc49a95ef2a9016bf9ce6"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310204dfed5aa797128b65d63538a9837cbdd15da2a29a77d67eefa489edda26"}, + {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0f9b92ea052305166559f38498b3b0cae159caea712646648aaa272f7160963"}, + {file = "tokenizers-0.13.3-cp38-cp38-win32.whl", hash = "sha256:9a3fa134896c3c1f0da6e762d15141fbff30d094067c8f1157b9fdca593b5806"}, + {file = "tokenizers-0.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:8e7b0cdeace87fa9e760e6a605e0ae8fc14b7d72e9fc19c578116f7287bb873d"}, + {file = "tokenizers-0.13.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:00cee1e0859d55507e693a48fa4aef07060c4bb6bd93d80120e18fea9371c66d"}, + {file = "tokenizers-0.13.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a23ff602d0797cea1d0506ce69b27523b07e70f6dda982ab8cf82402de839088"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ce07445050b537d2696022dafb115307abdffd2a5c106f029490f84501ef97"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:280ffe95f50eaaf655b3a1dc7ff1d9cf4777029dbbc3e63a74e65a056594abc3"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97acfcec592f7e9de8cadcdcda50a7134423ac8455c0166b28c9ff04d227b371"}, + {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7730c98a3010cd4f523465867ff95cd9d6430db46676ce79358f65ae39797b"}, + {file = "tokenizers-0.13.3-cp39-cp39-win32.whl", hash = "sha256:48625a108029cb1ddf42e17a81b5a3230ba6888a70c9dc14e81bc319e812652d"}, + {file = "tokenizers-0.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:bc0a6f1ba036e482db6453571c9e3e60ecd5489980ffd95d11dc9f960483d783"}, + {file = "tokenizers-0.13.3.tar.gz", hash = "sha256:2e546dbb68b623008a5442353137fbb0123d311a6d7ba52f2667c8862a75af2e"}, +] + +[package.extras] +dev = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] [[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" optional = false -python-versions = "*" -version = "1.4.1" +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.1" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, +] + +[[package]] +name = "torch" +version = "2.0.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +category = "main" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "torch-2.0.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ced00b3ba471856b993822508f77c98f48a458623596a4c43136158781e306a"}, + {file = "torch-2.0.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:359bfaad94d1cda02ab775dc1cc386d585712329bb47b8741607ef6ef4950747"}, + {file = "torch-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:7c84e44d9002182edd859f3400deaa7410f5ec948a519cc7ef512c2f9b34d2c4"}, + {file = "torch-2.0.1-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:567f84d657edc5582d716900543e6e62353dbe275e61cdc36eda4929e46df9e7"}, + {file = "torch-2.0.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:787b5a78aa7917465e9b96399b883920c88a08f4eb63b5a5d2d1a16e27d2f89b"}, + {file = "torch-2.0.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:e617b1d0abaf6ced02dbb9486803abfef0d581609b09641b34fa315c9c40766d"}, + {file = "torch-2.0.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b6019b1de4978e96daa21d6a3ebb41e88a0b474898fe251fd96189587408873e"}, + {file = "torch-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:dbd68cbd1cd9da32fe5d294dd3411509b3d841baecb780b38b3b7b06c7754434"}, + {file = "torch-2.0.1-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:ef654427d91600129864644e35deea761fb1fe131710180b952a6f2e2207075e"}, + {file = "torch-2.0.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:25aa43ca80dcdf32f13da04c503ec7afdf8e77e3a0183dd85cd3e53b2842e527"}, + {file = "torch-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5ef3ea3d25441d3957348f7e99c7824d33798258a2bf5f0f0277cbcadad2e20d"}, + {file = "torch-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0882243755ff28895e8e6dc6bc26ebcf5aa0911ed81b2a12f241fc4b09075b13"}, + {file = "torch-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:f66aa6b9580a22b04d0af54fcd042f52406a8479e2b6a550e3d9f95963e168c8"}, + {file = "torch-2.0.1-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:1adb60d369f2650cac8e9a95b1d5758e25d526a34808f7448d0bd599e4ae9072"}, + {file = "torch-2.0.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:1bcffc16b89e296826b33b98db5166f990e3b72654a2b90673e817b16c50e32b"}, + {file = "torch-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:e10e1597f2175365285db1b24019eb6f04d53dcd626c735fc502f1e8b6be9875"}, + {file = "torch-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:423e0ae257b756bb45a4b49072046772d1ad0c592265c5080070e0767da4e490"}, + {file = "torch-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8742bdc62946c93f75ff92da00e3803216c6cce9b132fbca69664ca38cfb3e18"}, + {file = "torch-2.0.1-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:c62df99352bd6ee5a5a8d1832452110435d178b5164de450831a3a8cc14dc680"}, + {file = "torch-2.0.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:671a2565e3f63b8fe8e42ae3e36ad249fe5e567435ea27b94edaa672a7d0c416"}, +] + +[package.dependencies] +filelock = "*" +jinja2 = "*" +networkx = "*" +sympy = "*" +typing-extensions = "*" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] + +[[package]] +name = "torchvision" +version = "0.15.2" +description = "image and video datasets and models for torch deep learning" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "torchvision-0.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7754088774e810c5672b142a45dcf20b1bd986a5a7da90f8660c43dc43fb850c"}, + {file = "torchvision-0.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37eb138e13f6212537a3009ac218695483a635c404b6cc1d8e0d0d978026a86d"}, + {file = "torchvision-0.15.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:54143f7cc0797d199b98a53b7d21c3f97615762d4dd17ad45a41c7e80d880e73"}, + {file = "torchvision-0.15.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:1eefebf5fbd01a95fe8f003d623d941601c94b5cec547b420da89cb369d9cf96"}, + {file = "torchvision-0.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:96fae30c5ca8423f4b9790df0f0d929748e32718d88709b7b567d2f630c042e3"}, + {file = "torchvision-0.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5f35f6bd5bcc4568e6522e4137fa60fcc72f4fa3e615321c26cd87e855acd398"}, + {file = "torchvision-0.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:757505a0ab2be7096cb9d2bf4723202c971cceddb72c7952a7e877f773de0f8a"}, + {file = "torchvision-0.15.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:012ad25cfd9019ff9b0714a168727e3845029be1af82296ff1e1482931fa4b80"}, + {file = "torchvision-0.15.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b02a7ffeaa61448737f39a4210b8ee60234bda0515a0c0d8562f884454105b0f"}, + {file = "torchvision-0.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:10be76ceded48329d0a0355ac33da131ee3993ff6c125e4a02ab34b5baa2472c"}, + {file = "torchvision-0.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f12415b686dba884fb086f53ac803f692be5a5cdd8a758f50812b30fffea2e4"}, + {file = "torchvision-0.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:31211c01f8b8ec33b8a638327b5463212e79a03e43c895f88049f97af1bd12fd"}, + {file = "torchvision-0.15.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c55f9889e436f14b4f84a9c00ebad0d31f5b4626f10cf8018e6c676f92a6d199"}, + {file = "torchvision-0.15.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9a192f2aa979438f23c20e883980b23d13268ab9f819498774a6d2eb021802c2"}, + {file = "torchvision-0.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:c07071bc8d02aa8fcdfe139ab6a1ef57d3b64c9e30e84d12d45c9f4d89fb6536"}, + {file = "torchvision-0.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4790260fcf478a41c7ecc60a6d5200a88159fdd8d756e9f29f0f8c59c4a67a68"}, + {file = "torchvision-0.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:987ab62225b4151a11e53fd06150c5258ced24ac9d7c547e0e4ab6fbca92a5ce"}, + {file = "torchvision-0.15.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:63df26673e66cba3f17e07c327a8cafa3cce98265dbc3da329f1951d45966838"}, + {file = "torchvision-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b85f98d4cc2f72452f6792ab4463a3541bc5678a8cdd3da0e139ba2fe8b56d42"}, + {file = "torchvision-0.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:07c462524cc1bba5190c16a9d47eac1fca024d60595a310f23c00b4ffff18b30"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=5.3.0,<8.3.0 || >=8.4.0" +requests = "*" +torch = "2.0.1" + +[package.extras] +scipy = ["scipy"] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] [[package]] +name = "transformers" +version = "4.33.3" +description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" category = "main" -description = "Backported and Experimental Type Hints for Python 3.5+" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "transformers-4.33.3-py3-none-any.whl", hash = "sha256:7150bbf6781ddb3338ce7d74f4d6f557e6c236a0a1dd3de57412214caae7fd71"}, + {file = "transformers-4.33.3.tar.gz", hash = "sha256:8ea7c92310dee7c63b14766ce928218f7a9177960b2487ac018c91ae621af03e"}, +] + +[package.dependencies] +filelock = "*" +huggingface-hub = ">=0.15.1,<1.0" +numpy = ">=1.17" +packaging = ">=20.0" +pyyaml = ">=5.1" +regex = "!=2019.12.17" +requests = "*" +safetensors = ">=0.3.1" +tokenizers = ">=0.11.1,<0.11.3 || >0.11.3,<0.14" +tqdm = ">=4.27" + +[package.extras] +accelerate = ["accelerate (>=0.20.3)"] +agents = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=1.10,!=1.12.0)"] +all = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +codecarbon = ["codecarbon (==1.2.0)"] +deepspeed = ["accelerate (>=0.20.3)", "deepspeed (>=0.9.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.20.3)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "accelerate (>=0.20.3)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +docs = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "hf-doc-builder", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] +docs-specific = ["hf-doc-builder"] +fairscale = ["fairscale (>0.3)"] +flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +ftfy = ["ftfy"] +integrations = ["optuna", "ray[tune]", "sigopt"] +ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] +modelcreation = ["cookiecutter (==1.7.3)"] +natten = ["natten (>=0.14.6)"] +onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] +onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +optuna = ["optuna"] +quality = ["GitPython (<3.1.19)", "black (>=23.1,<24.0)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (>=0.0.241,<=0.0.259)", "urllib3 (<2.0.0)"] +ray = ["ray[tune]"] +retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +sagemaker = ["sagemaker (>=2.31.0)"] +sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] +serving = ["fastapi", "pydantic (<2)", "starlette", "uvicorn"] +sigopt = ["sigopt"] +sklearn = ["scikit-learn"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "parameterized", "protobuf", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "timeout-decorator"] +tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx"] +tf-cpu = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +timm = ["timm"] +tokenizers = ["tokenizers (>=0.11.1,!=0.11.3,<0.14)"] +torch = ["accelerate (>=0.20.3)", "torch (>=1.10,!=1.12.0)"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch-vision = ["Pillow (<10.0.0)", "torchvision"] +torchhub = ["filelock", "huggingface-hub (>=0.15.1,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.10,!=1.12.0)", "tqdm (>=4.27)"] +video = ["av (==9.2.0)", "decord (==0.6.0)"] +vision = ["Pillow (<10.0.0)"] + +[[package]] name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false -python-versions = "*" -version = "3.7.4.3" +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] [[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." category = "main" -description = "tzinfo object for the local timezone" -name = "tzlocal" optional = false python-versions = "*" -version = "2.1" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] [package.dependencies] -pytz = "*" +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" [[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" category = "main" -description = "Ultra fast JSON encoder and decoder for Python" -name = "ujson" optional = false -python-versions = "*" -version = "1.35" +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] [[package]] +name = "tzlocal" +version = "5.0.1" +description = "tzinfo object for the local timezone" category = "main" -description = "URI templates" -name = "uritemplate" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.1" +python-versions = ">=3.7" +files = [ + {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, + {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] [[package]] +name = "unstructured" +version = "0.10.18" +description = "A library that prepares raw documents for downstream ML tasks." category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "unstructured-0.10.18-py3-none-any.whl", hash = "sha256:eaec0f0ecc470bb646a750cb32c125275d34d258ced46cfc3364098939d9ca77"}, + {file = "unstructured-0.10.18.tar.gz", hash = "sha256:7f330573d4297182f4b1500e05c9fc4779a08811bce23c527a96898b2ff374f6"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +chardet = "*" +dataclasses-json = "*" +emoji = "*" +filetype = "*" +langdetect = "*" +lxml = "*" +nltk = "*" +numpy = "*" +python-iso639 = "*" +python-magic = "*" +requests = "*" +tabulate = "*" + +[package.extras] +airtable = ["pyairtable"] +all-docs = ["ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx (<=0.6.21)", "unstructured-inference (==0.5.31)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +azure = ["adlfs", "fsspec (==2023.9.1)"] +azure-cognitive-search = ["azure-search-documents"] +biomed = ["bs4"] +box = ["boxfs", "fsspec (==2023.9.1)"] +confluence = ["atlassian-python-api"] +csv = ["pandas"] +delta-table = ["deltalake", "fsspec (==2023.9.1)"] +discord = ["discord-py"] +doc = ["python-docx"] +docx = ["python-docx"] +dropbox = ["dropboxdrivefs", "fsspec (==2023.9.1)"] +elasticsearch = ["elasticsearch", "jq"] +epub = ["ebooklib"] +gcs = ["bs4", "fsspec (==2023.9.1)", "gcsfs"] +github = ["pygithub (>1.58.0)"] +gitlab = ["python-gitlab"] +google-drive = ["google-api-python-client"] +huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"] +image = ["pdf2image", "pdfminer.six", "unstructured-inference (==0.5.31)", "unstructured.pytesseract (>=0.3.12)"] +jira = ["atlassian-python-api"] +local-inference = ["ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx (<=0.6.21)", "unstructured-inference (==0.5.31)", "unstructured.pytesseract (>=0.3.12)", "xlrd"] +md = ["markdown"] +msg = ["msg-parser"] +notion = ["htmlBuilder", "notion-client"] +odt = ["pypandoc", "python-docx"] +onedrive = ["Office365-REST-Python-Client (<2.4.3)", "bs4", "msal"] +openai = ["langchain", "openai", "tiktoken"] +org = ["pypandoc"] +outlook = ["Office365-REST-Python-Client (<2.4.3)", "msal"] +paddleocr = ["unstructured.paddleocr (==2.6.1.3)"] +pdf = ["pdf2image", "pdfminer.six", "unstructured-inference (==0.5.31)", "unstructured.pytesseract (>=0.3.12)"] +ppt = ["python-pptx (<=0.6.21)"] +pptx = ["python-pptx (<=0.6.21)"] +reddit = ["praw"] +rst = ["pypandoc"] +rtf = ["pypandoc"] +s3 = ["fsspec (==2023.9.1)", "s3fs"] +salesforce = ["simple-salesforce"] +sharepoint = ["Office365-REST-Python-Client (<2.4.3)", "msal"] +slack = ["slack-sdk"] +tsv = ["pandas"] +wikipedia = ["wikipedia"] +xlsx = ["openpyxl", "pandas", "xlrd"] + +[[package]] name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.10" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] +name = "uvicorn" +version = "0.23.2" +description = "The lightning-fast ASGI server." category = "main" -description = "Yet another URL library" -name = "yarl" optional = false -python-versions = ">=3.5" -version = "1.6.0" +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, + {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, +] [package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} -[package.dependencies.typing-extensions] -python = "<3.8" -version = ">=3.7.4" +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" category = "main" -description = "No-SQLite U.S. zipcode validation Python package, ready for use in AWS Lambda" -name = "zipcodes" optional = false -python-versions = "*" -version = "1.1.2" +python-versions = ">=3.7" +files = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "validators" +version = "0.22.0" +description = "Python Data Validation for Humans™" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "validators-0.22.0-py3-none-any.whl", hash = "sha256:61cf7d4a62bbae559f2e54aed3b000cea9ff3e2fdbe463f51179b92c58c9585a"}, + {file = "validators-0.22.0.tar.gz", hash = "sha256:77b2689b172eeeb600d9605ab86194641670cdb73b60afd577142a9397873370"}, +] + +[package.extras] +docs-offline = ["myst-parser (>=2.0.0)", "pypandoc-binary (>=1.11)", "sphinx (>=7.1.1)"] +docs-online = ["mkdocs (>=1.5.2)", "mkdocs-git-revision-date-localized-plugin (>=1.2.0)", "mkdocs-material (>=9.2.6)", "mkdocstrings[python] (>=0.22.0)", "pyaml (>=23.7.0)"] +hooks = ["pre-commit (>=3.3.3)"] +package = ["build (>=1.0.0)", "twine (>=4.0.2)"] +runner = ["tox (>=4.11.1)"] +sast = ["bandit[toml] (>=1.7.5)"] +testing = ["pytest (>=7.4.0)"] +tooling = ["black (>=23.7.0)", "pyright (>=1.1.325)", "ruff (>=0.0.287)"] +tooling-extras = ["pyaml (>=23.7.0)", "pypandoc-binary (>=1.11)", "pytest (>=7.4.0)"] [[package]] +name = "vcrpy" +version = "5.1.0" +description = "Automatically mock your HTTP interactions to simplify and speed up testing" category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" -name = "zipp" optional = false -python-versions = ">=3.6" -version = "3.2.0" +python-versions = ">=3.8" +files = [ + {file = "vcrpy-5.1.0-py2.py3-none-any.whl", hash = "sha256:605e7b7a63dcd940db1df3ab2697ca7faf0e835c0852882142bafb19649d599e"}, + {file = "vcrpy-5.1.0.tar.gz", hash = "sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2"}, +] + +[package.dependencies] +PyYAML = "*" +wrapt = "*" +yarl = "*" + +[[package]] +name = "watchfiles" +version = "0.20.0" +description = "Simple, modern and high performance file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchfiles-0.20.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:3796312bd3587e14926013612b23066912cf45a14af71cf2b20db1c12dadf4e9"}, + {file = "watchfiles-0.20.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d0002d81c89a662b595645fb684a371b98ff90a9c7d8f8630c82f0fde8310458"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:570848706440373b4cd8017f3e850ae17f76dbdf1e9045fc79023b11e1afe490"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a0351d20d03c6f7ad6b2e8a226a5efafb924c7755ee1e34f04c77c3682417fa"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:007dcc4a401093010b389c044e81172c8a2520dba257c88f8828b3d460c6bb38"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d82dbc1832da83e441d112069833eedd4cf583d983fb8dd666fbefbea9d99c0"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99f4c65fd2fce61a571b2a6fcf747d6868db0bef8a934e8ca235cc8533944d95"}, + {file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5392dd327a05f538c56edb1c6ebba6af91afc81b40822452342f6da54907bbdf"}, + {file = "watchfiles-0.20.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:08dc702529bb06a2b23859110c214db245455532da5eaea602921687cfcd23db"}, + {file = "watchfiles-0.20.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d4e66a857621584869cfbad87039e65dadd7119f0d9bb9dbc957e089e32c164"}, + {file = "watchfiles-0.20.0-cp37-abi3-win32.whl", hash = "sha256:a03d1e6feb7966b417f43c3e3783188167fd69c2063e86bad31e62c4ea794cc5"}, + {file = "watchfiles-0.20.0-cp37-abi3-win_amd64.whl", hash = "sha256:eccc8942bcdc7d638a01435d915b913255bbd66f018f1af051cd8afddb339ea3"}, + {file = "watchfiles-0.20.0-cp37-abi3-win_arm64.whl", hash = "sha256:b17d4176c49d207865630da5b59a91779468dd3e08692fe943064da260de2c7c"}, + {file = "watchfiles-0.20.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d97db179f7566dcf145c5179ddb2ae2a4450e3a634eb864b09ea04e68c252e8e"}, + {file = "watchfiles-0.20.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:835df2da7a5df5464c4a23b2d963e1a9d35afa422c83bf4ff4380b3114603644"}, + {file = "watchfiles-0.20.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608cd94a8767f49521901aff9ae0c92cc8f5a24d528db7d6b0295290f9d41193"}, + {file = "watchfiles-0.20.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89d1de8218874925bce7bb2ae9657efc504411528930d7a83f98b1749864f2ef"}, + {file = "watchfiles-0.20.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:13f995d5152a8ba4ed7c2bbbaeee4e11a5944defc7cacd0ccb4dcbdcfd78029a"}, + {file = "watchfiles-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b5c8d3be7b502f8c43a33c63166ada8828dbb0c6d49c8f9ce990a96de2f5a49"}, + {file = "watchfiles-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e43af4464daa08723c04b43cf978ab86cc55c684c16172622bdac64b34e36af0"}, + {file = "watchfiles-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d9e1f75c4f86c93d73b5bd1ebe667558357548f11b4f8af4e0e272f79413ce"}, + {file = "watchfiles-0.20.0.tar.gz", hash = "sha256:728575b6b94c90dd531514677201e8851708e6e4b5fe7028ac506a200b622019"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "weaviate-client" +version = "3.24.1" +description = "A python native Weaviate client" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "weaviate-client-3.24.1.tar.gz", hash = "sha256:e073350c21bd4dca5c0eac5dd5d95fb474278c715aaf2041e382d86bb7518ac8"}, + {file = "weaviate_client-3.24.1-py3-none-any.whl", hash = "sha256:2179ea1093685f6f7cefbd19e2896eeb4b4c720954d953018c0853f775cdad8b"}, +] + +[package.dependencies] +authlib = ">=1.2.1,<2.0.0" +requests = ">=2.30.0,<3.0.0" +validators = ">=0.21.2,<1.0.0" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +grpc = ["grpcio (>=1.57.0,<2.0.0)", "grpcio-tools (>=1.57.0,<2.0.0)"] -[metadata] -content-hash = "2d587eba0ef9a049615cc107242e107195490bfdc7f3182b64ac397dd6a110e4" -python-versions = "^3.7" - -[metadata.files] -aiocontextvars = [ - {file = "aiocontextvars-0.2.2-py2.py3-none-any.whl", hash = "sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3"}, - {file = "aiocontextvars-0.2.2.tar.gz", hash = "sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"}, -] -aiofiles = [ - {file = "aiofiles-0.4.0-py3-none-any.whl", hash = "sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"}, - {file = "aiofiles-0.4.0.tar.gz", hash = "sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee"}, -] -aiohttp = [ - {file = "aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e"}, - {file = "aiohttp-3.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec"}, - {file = "aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48"}, - {file = "aiohttp-3.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59"}, - {file = "aiohttp-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a"}, - {file = "aiohttp-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17"}, - {file = "aiohttp-3.6.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a"}, - {file = "aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd"}, - {file = "aiohttp-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"}, - {file = "aiohttp-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654"}, - {file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"}, - {file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"}, -] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -apscheduler = [ - {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, - {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, -] -async-timeout = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, -] -asyncio-contextmanager = [ - {file = "asyncio-contextmanager-1.0.1.tar.gz", hash = "sha256:93b4620cd79623c3988c9f43e6f502263968645cd13aed3327a7ec8be43221d5"}, -] -asyncpg = [ - {file = "asyncpg-0.18.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:814343dc2baa489a11521ff9fad68f337a05c9ae0461fdf9f1ec7ac3541c13a9"}, - {file = "asyncpg-0.18.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:89e727fdba05d90a0156d9d18932fd44a2baa84e90e3368573f432a308ad8fd7"}, - {file = "asyncpg-0.18.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:84084f7dfed0b2d397a0c2fd7eaf29b01904c74f4320e5fe95ad3042042cf188"}, - {file = "asyncpg-0.18.3-cp35-cp35m-win32.whl", hash = "sha256:378a7ef11ce7b35f11eb816e5252bc1e779119f7583a872233b45a76effac02e"}, - {file = "asyncpg-0.18.3-cp35-cp35m-win_amd64.whl", hash = "sha256:fd2d13da29f55c2c71b1acc9d9f107c7a5176fffb3f62ff503f2b300f7ecd74e"}, - {file = "asyncpg-0.18.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:51a3d67a3fa43112b17ec510338723932e1e0611ad99a146acc9960d32210196"}, - {file = "asyncpg-0.18.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4539bc2e63600a1ee999086bbb59bf717ab32ea771ac20b5b792a2234633b5fb"}, - {file = "asyncpg-0.18.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4a779a85302241782bed8ed0f2bcb38544805b3e107b16ee7489c5818d8f4228"}, - {file = "asyncpg-0.18.3-cp36-cp36m-win32.whl", hash = "sha256:c1fe1f0ef848f0f17bf63b90a4c3f446a14e4c899d8531ea988109cc0de014e5"}, - {file = "asyncpg-0.18.3-cp36-cp36m-win_amd64.whl", hash = "sha256:cc7aa61bf41273ee5d4c11e0e72c0d9340e9c4dbf752464ae2b6816abadaabce"}, - {file = "asyncpg-0.18.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:ab8b9d367e3ef48f35a059642940714a2bda7a7fce8b017b21bfbc4f8fbf8f5f"}, - {file = "asyncpg-0.18.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:d5450bdf8631fa1200c08a2e70cab06c2e8c09ef608629908531513444d12858"}, - {file = "asyncpg-0.18.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0677714b26b48d63db728867b812ef365ec3879d2be6fa1c9cf4328503f9a464"}, - {file = "asyncpg-0.18.3-cp37-cp37m-win32.whl", hash = "sha256:fd35a8082b97d5b97d26bcd1b010fdd65a56311d7a02bf2a7e2c56810b9961a7"}, - {file = "asyncpg-0.18.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2dee4fb251139f1c1ee4bd9959d516f930f4da37a2f33b07c2b902b837a76666"}, - {file = "asyncpg-0.18.3.tar.gz", hash = "sha256:58a5eccaac60fd326e32683226efe1046bfea558fa043360bdd1708e0e812c67"}, -] -asynctest = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, - {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, -] -black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, -] -cchardet = [ - {file = "cchardet-2.1.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:2aa1b008965c703ad6597361b0f6d427c8971fe94a2c99ec3724c228ae50d6a6"}, - {file = "cchardet-2.1.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fd16f57ce42a72397cd9fe38977fc809eb02172731cb354572f28a6d8e4cf322"}, - {file = "cchardet-2.1.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:953fe382304b19f5aa8fc2da4b092a3bb58a477d33af4def4b81abdce4c9288c"}, - {file = "cchardet-2.1.6-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:ccb9f6f06265382028468b47e726f2d42539256fb498d1b0e473c39037b42b8a"}, - {file = "cchardet-2.1.6-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:2c05b66b12f9ab0493c5ffb666036fd8c9004a9cc9d5a9264dc24738b50ab8c3"}, - {file = "cchardet-2.1.6-cp35-cp35m-win32.whl", hash = "sha256:dff9480d9b6260f59ad10e1cec5be13905be5da88a4a2bd5a5bd4d49c49c4a05"}, - {file = "cchardet-2.1.6-cp35-cp35m-win_amd64.whl", hash = "sha256:84d2ce838cf3c2fe7f0517941702d42f7e598e5173632ec47a113cd521669b98"}, - {file = "cchardet-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4486f6e5bdf06f0081d13832f2a061d9e90597eb02093fda9d37e3985e3b2ef2"}, - {file = "cchardet-2.1.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7a2d98df461d3f36b403fdd8d7890c823ed05bd98eb074412ed56fbfedb94751"}, - {file = "cchardet-2.1.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:27b0f23088873d1dd36d2c8a2e45c9167e312e1aac7e4baeb47f7428a2669638"}, - {file = "cchardet-2.1.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:cf134e1cfb0c53f08abb1ab9158a7e7f859c3ddb451d5fe535a2cc5f2958a688"}, - {file = "cchardet-2.1.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f5c94994d876d8709847c3a92643309d716f43716580a2e5831262366a9ee8b6"}, - {file = "cchardet-2.1.6-cp36-cp36m-win32.whl", hash = "sha256:217a7008bd399bdb61f6a0a2570acc5c3a9f96140e0a0d089b9e748c4d4e4c4e"}, - {file = "cchardet-2.1.6-cp36-cp36m-win_amd64.whl", hash = "sha256:2a958fb093f69ee5f16be7a1aee5122e07aff4350fa4dc9b953b87c34468e605"}, - {file = "cchardet-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4096759825a130cb27a58ddf6d58e10abdd0127d29fbf53fde26df7ad879737b"}, - {file = "cchardet-2.1.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:7bba1cbb4358dc9a2d2da00f4b38b159a5483d2f3b1d698a7c2cae518f955170"}, - {file = "cchardet-2.1.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0f6e4e464e332da776b9c1a34e4e83b6301d38c2724efc93848c46ade66d02bb"}, - {file = "cchardet-2.1.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:acc96b4a8f756af289fa90ffa67ddef57401d99131e51e71872e3609483941ce"}, - {file = "cchardet-2.1.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:79b0e113144c2ef0050bc9fe647c7657c5298f3012ecd8937d930b24ddd61404"}, - {file = "cchardet-2.1.6-cp37-cp37m-win32.whl", hash = "sha256:8b1d02c99f6444c63336a76638741eaf4ac4005b454e3b8252a40074bf0d84a1"}, - {file = "cchardet-2.1.6-cp37-cp37m-win_amd64.whl", hash = "sha256:e27771798c8ad50df1375e762d59369354af94eb8ac21eca5bfd1eeef589f545"}, - {file = "cchardet-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:68409e00d75ff13dd7a192ec49559f5527ee8959a51a9f4dd7b168df972b4d44"}, - {file = "cchardet-2.1.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:40c199f9c0569ac479fae7c4e12d2e16fc1e8237836b928474fdd228b8d11477"}, - {file = "cchardet-2.1.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8f7ade2578b2326a0a554c03f60c8d079331220179a592e83e143c9556b7f5b2"}, - {file = "cchardet-2.1.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:5e38cfad9d3ca0f571c4352e9ca0f5ab718508f492a37d3236ae70810140e250"}, - {file = "cchardet-2.1.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:af284494ea6c40f9613b4d939abe585eb9290cb92037eab66122c93190fcb338"}, - {file = "cchardet-2.1.6-cp38-cp38-win32.whl", hash = "sha256:54d2653520237ebbd2928f2c0f2eb7c616ee2b5194d73d945060cd54a7846b64"}, - {file = "cchardet-2.1.6-cp38-cp38-win_amd64.whl", hash = "sha256:f245f045054e8d6dab2a0e366d3c74f3a47fb7dec2595ae2035b234b1a829c7a"}, - {file = "cchardet-2.1.6.tar.gz", hash = "sha256:b76afb2059ad69eab576949980a17413c1e9e5a5624abf9e43542d8853f146b3"}, -] -certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -cython = [ - {file = "Cython-0.29.21-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c541b2b49c6638f2b5beb9316726db84a8d1c132bf31b942dae1f9c7f6ad3b92"}, - {file = "Cython-0.29.21-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b8d8497091c1dc8705d1575c71e908a93b1f127a174b2d472020f3d84263ac28"}, - {file = "Cython-0.29.21-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:695a6bcaf9e12b1e471dfce96bbecf22a1487adc2ac6106b15960a2b51b97f5d"}, - {file = "Cython-0.29.21-cp27-cp27m-win32.whl", hash = "sha256:171b9f70ceafcec5852089d0f9c1e75b0d554f46c882cd4e2e4acaba9bd7d148"}, - {file = "Cython-0.29.21-cp27-cp27m-win_amd64.whl", hash = "sha256:539e59949aab4955c143a468810123bf22d3e8556421e1ce2531ed4893914ca0"}, - {file = "Cython-0.29.21-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e93acd1f603a0c1786e0841f066ae7cef014cf4750e3cd06fd03cfdf46361419"}, - {file = "Cython-0.29.21-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:2922e3031ba9ebbe7cb9200b585cc33b71d66023d78450dcb883f824f4969371"}, - {file = "Cython-0.29.21-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:497841897942f734b0abc2dead2d4009795ee992267a70a23485fd0e937edc0b"}, - {file = "Cython-0.29.21-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:0ac10bf476476a9f7ef61ec6e44c280ef434473124ad31d3132b720f7b0e8d2a"}, - {file = "Cython-0.29.21-cp34-cp34m-win32.whl", hash = "sha256:31c71a615f38401b0dc1f2a5a9a6c421ffd8908c4cd5bbedc4014c1b876488e8"}, - {file = "Cython-0.29.21-cp34-cp34m-win_amd64.whl", hash = "sha256:c4b78356074fcaac04ecb4de289f11d506e438859877670992ece11f9c90f37b"}, - {file = "Cython-0.29.21-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:b2f9172e4d6358f33ecce6a4339b5960f9f83eab67ea244baa812737793826b7"}, - {file = "Cython-0.29.21-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:856c7fb31d247ce713d60116375e1f8153d0291ab5e92cca7d8833a524ba9991"}, - {file = "Cython-0.29.21-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:715294cd2246b39a8edca464a8366eb635f17213e4a6b9e74e52d8b877a8cb63"}, - {file = "Cython-0.29.21-cp35-cp35m-win32.whl", hash = "sha256:23f3a00b843a19de8bb4468b087db5b413a903213f67188729782488d67040e0"}, - {file = "Cython-0.29.21-cp35-cp35m-win_amd64.whl", hash = "sha256:ccb77faeaad99e99c6c444d04862c6cf604204fe0a07d4c8f9cbf2c9012d7d5a"}, - {file = "Cython-0.29.21-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e272ed97d20b026f4f25a012b25d7d7672a60e4f72b9ca385239d693cd91b2d5"}, - {file = "Cython-0.29.21-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8c6e25e9cc4961bb2abb1777c6fa9d0fa2d9b014beb3276cebe69996ff162b78"}, - {file = "Cython-0.29.21-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:57ead89128dee9609119c93d3926c7a2add451453063147900408a50144598c6"}, - {file = "Cython-0.29.21-cp36-cp36m-win32.whl", hash = "sha256:0e25c209c75df8785480dcef85db3d36c165dbc0f4c503168e8763eb735704f2"}, - {file = "Cython-0.29.21-cp36-cp36m-win_amd64.whl", hash = "sha256:a0674f246ad5e1571ef29d4c5ec1d6ecabe9e6c424ad0d6fee46b914d5d24d69"}, - {file = "Cython-0.29.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5da187bebe38030325e1c0b5b8a804d489410be2d384c0ef3ba39493c67eb51e"}, - {file = "Cython-0.29.21-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9ce5e5209f8406ffc2b058b1293cce7a954911bb7991e623564d489197c9ba30"}, - {file = "Cython-0.29.21-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5e545a48f919e40079b0efe7b0e081c74b96f9ef25b9c1ff4cdbd95764426b58"}, - {file = "Cython-0.29.21-cp37-cp37m-win32.whl", hash = "sha256:c8435959321cf8aec867bbad54b83b7fb8343204b530d85d9ea7a1f5329d5ac2"}, - {file = "Cython-0.29.21-cp37-cp37m-win_amd64.whl", hash = "sha256:540b3bee0711aac2e99bda4fa0a46dbcd8c74941666bfc1ef9236b1a64eeffd9"}, - {file = "Cython-0.29.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:93f5fed1c9445fb7afe20450cdaf94b0e0356d47cc75008105be89c6a2e417b1"}, - {file = "Cython-0.29.21-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9207fdedc7e789a3dcaca628176b80c82fbed9ae0997210738cbb12536a56699"}, - {file = "Cython-0.29.21-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:603b9f1b8e93e8b494d3e89320c410679e21018e48b6cbc77280f5db71f17dc0"}, - {file = "Cython-0.29.21-cp38-cp38-win32.whl", hash = "sha256:473df5d5e400444a36ed81c6596f56a5b52a3481312d0a48d68b777790f730ae"}, - {file = "Cython-0.29.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8a8a31b9e8860634adbca30fea1d0c7f08e208b3d7611f3e580e5f20992e5d7"}, - {file = "Cython-0.29.21-py2.py3-none-any.whl", hash = "sha256:5c4276fdcbccdf1e3c1756c7aeb8395e9a36874fa4d30860e7694f43d325ae13"}, - {file = "Cython-0.29.21.tar.gz", hash = "sha256:e57acb89bd55943c8d8bf813763d20b9099cc7165c0f16b707631a7654be9cad"}, -] -flake8 = [ - {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, - {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, -] -gidgethub = [ - {file = "gidgethub-3.3.0-py3-none-any.whl", hash = "sha256:4a456758a5fc8bfd581f297df90f2d09efbb830ccd209b1ceba4723705607d70"}, - {file = "gidgethub-3.3.0.tar.gz", hash = "sha256:3692d2df48a23c87ec4a5e74053ce343bc59cea7c34488a9136754a35aeb177a"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -importlib-metadata = [ - {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, - {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, -] -iniconfig = [ - {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, - {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, -] -isort = [ - {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, - {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -multidict = [ - {file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"}, - {file = "multidict-4.7.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a"}, - {file = "multidict-4.7.6-cp35-cp35m-win32.whl", hash = "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5"}, - {file = "multidict-4.7.6-cp35-cp35m-win_amd64.whl", hash = "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3"}, - {file = "multidict-4.7.6-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87"}, - {file = "multidict-4.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2"}, - {file = "multidict-4.7.6-cp36-cp36m-win32.whl", hash = "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7"}, - {file = "multidict-4.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463"}, - {file = "multidict-4.7.6-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"}, - {file = "multidict-4.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255"}, - {file = "multidict-4.7.6-cp37-cp37m-win32.whl", hash = "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507"}, - {file = "multidict-4.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c"}, - {file = "multidict-4.7.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b"}, - {file = "multidict-4.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7"}, - {file = "multidict-4.7.6-cp38-cp38-win32.whl", hash = "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d"}, - {file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"}, - {file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, -] -pathspec = [ - {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, - {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, -] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, -] -pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, -] -pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pytest = [ - {file = "pytest-6.1.0-py3-none-any.whl", hash = "sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33"}, - {file = "pytest-6.1.0.tar.gz", hash = "sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7"}, -] -pytest-aiohttp = [ - {file = "pytest-aiohttp-0.3.0.tar.gz", hash = "sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f"}, - {file = "pytest_aiohttp-0.3.0-py3-none-any.whl", hash = "sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, - {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, -] -pytest-mock = [ - {file = "pytest-mock-3.3.1.tar.gz", hash = "sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82"}, - {file = "pytest_mock-3.3.1-py3-none-any.whl", hash = "sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2"}, -] -python-dotenv = [ - {file = "python-dotenv-0.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"}, - {file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"}, -] -pytz = [ - {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, - {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, -] -pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, -] -regex = [ - {file = "regex-2020.9.27-cp27-cp27m-win32.whl", hash = "sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3"}, - {file = "regex-2020.9.27-cp27-cp27m-win_amd64.whl", hash = "sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19"}, - {file = "regex-2020.9.27-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be"}, - {file = "regex-2020.9.27-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b"}, - {file = "regex-2020.9.27-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc"}, - {file = "regex-2020.9.27-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b"}, - {file = "regex-2020.9.27-cp36-cp36m-win32.whl", hash = "sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63"}, - {file = "regex-2020.9.27-cp36-cp36m-win_amd64.whl", hash = "sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b"}, - {file = "regex-2020.9.27-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7"}, - {file = "regex-2020.9.27-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc"}, - {file = "regex-2020.9.27-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c"}, - {file = "regex-2020.9.27-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100"}, - {file = "regex-2020.9.27-cp37-cp37m-win32.whl", hash = "sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707"}, - {file = "regex-2020.9.27-cp37-cp37m-win_amd64.whl", hash = "sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab"}, - {file = "regex-2020.9.27-cp38-cp38-manylinux1_i686.whl", hash = "sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef"}, - {file = "regex-2020.9.27-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121"}, - {file = "regex-2020.9.27-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b"}, - {file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"}, - {file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"}, - {file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"}, - {file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"}, -] -requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, -] -sentry-sdk = [ - {file = "sentry-sdk-0.17.8.tar.gz", hash = "sha256:e159f7c919d19ae86e5a4ff370fccc45149fab461fbeb93fb5a735a0b33a9cb1"}, - {file = "sentry_sdk-0.17.8-py2.py3-none-any.whl", hash = "sha256:c9c0fa1412bad87104c4eee8dd36c7bbf60b0d92ae917ab519094779b22e6d9a"}, -] -sirbot = [ - {file = "sirbot-0.1.1-py3-none-any.whl", hash = "sha256:3252a917f6336f37fd95223f472cc0e225dac98b6b3492c5ce9953d8cda3cc4f"}, - {file = "sirbot-0.1.1.tar.gz", hash = "sha256:97be6915ec814e76d7ecce504c1c037834d6be1dc618c3032653d6ecac0f1c0b"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -slack-sansio = [ - {file = "slack-sansio-1.1.0.tar.gz", hash = "sha256:4dec16e6f9ced6003de201c5e3bd1dbfc053ab8c8772ab29529772805b8a18a1"}, - {file = "slack_sansio-1.1.0-py3-none-any.whl", hash = "sha256:0403c02ba6c3e57f6de7e9522aef7973bd71e33453f5b2ea760ec513b619750a"}, -] -toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, -] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, -] -typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, -] -tzlocal = [ - {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, - {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, -] -ujson = [ - {file = "ujson-1.35.tar.gz", hash = "sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"}, -] -uritemplate = [ - {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, - {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, -] -urllib3 = [ - {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, - {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, -] -yarl = [ - {file = "yarl-1.6.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1"}, - {file = "yarl-1.6.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188"}, - {file = "yarl-1.6.0-cp35-cp35m-win32.whl", hash = "sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580"}, - {file = "yarl-1.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc"}, - {file = "yarl-1.6.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a"}, - {file = "yarl-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a"}, - {file = "yarl-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e"}, - {file = "yarl-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2"}, - {file = "yarl-1.6.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e"}, - {file = "yarl-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a"}, - {file = "yarl-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131"}, - {file = "yarl-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d"}, - {file = "yarl-1.6.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921"}, - {file = "yarl-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1"}, - {file = "yarl-1.6.0-cp38-cp38-win32.whl", hash = "sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5"}, - {file = "yarl-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020"}, - {file = "yarl-1.6.0.tar.gz", hash = "sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b"}, -] -zipcodes = [ - {file = "zipcodes-1.1.2-py2.py3-none-any.whl", hash = "sha256:66b69078c7336b1cd32d032d34251d9e961a8842a9add5e08dfcb628d8c4bc5e"}, - {file = "zipcodes-1.1.2.tar.gz", hash = "sha256:20673593a25c0b5c04ed5c1c0ed68be88e96ae79b0f1d12946809084165e5afb"}, -] -zipp = [ - {file = "zipp-3.2.0-py3-none-any.whl", hash = "sha256:43f4fa8d8bb313e65d8323a3952ef8756bf40f9a5c3ea7334be23ee4ec8278b6"}, - {file = "zipp-3.2.0.tar.gz", hash = "sha256:b52f22895f4cfce194bc8172f3819ee8de7540aa6d873535a8668b730b8b411f"}, +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[[package]] +name = "werkzeug" +version = "3.0.0" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, + {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, ] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "~3.10" +content-hash = "43acf08ba229248af51e5364533c9b1072081d483b1c4925bf94d16a3124847e" diff --git a/pybot/__init__.py b/pybot/__init__.py deleted file mode 100644 index bed8b886..00000000 --- a/pybot/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -from pathlib import Path - -from dotenv import load_dotenv - -""" Loads values from .env file for local development """ -url = Path(os.path.dirname(os.path.dirname(__file__))) / "docker" / "pybot.env" -load_dotenv(dotenv_path=url) diff --git a/pybot/__main__.py b/pybot/__main__.py deleted file mode 100644 index b2a7b672..00000000 --- a/pybot/__main__.py +++ /dev/null @@ -1,63 +0,0 @@ -import logging.config -import os - -import sentry_sdk -import yaml -from sentry_sdk.integrations.aiohttp import AioHttpIntegration -from sirbot import SirBot -from sirbot.plugins.slack import SlackPlugin - -from pybot.endpoints import handle_health_check -from pybot.endpoints.slack.utils import HOST, PORT, slack_configs - -from . import endpoints -from .plugins import AirtablePlugin, APIPlugin - -logger = logging.getLogger(__name__) - -if __name__ == "__main__": - try: - with open( - os.path.join(os.path.dirname(os.path.realpath(__file__)), "../logging.yml") - ) as log_configfile: - logging.config.dictConfig( - yaml.load(log_configfile.read(), Loader=yaml.SafeLoader) - ) - except Exception as e: - logging.basicConfig(level=logging.DEBUG) - logger.exception(e) - - if "SENTRY_DSN" in os.environ: - sentry_sdk.init( - dsn=os.environ["SENTRY_DSN"], - release=os.environ.get("VERSION", "1.0.0"), - environment=os.environ.get("ENVIRONMENT", "production"), - integrations=[AioHttpIntegration()], - ) - - bot = SirBot() - - slack = SlackPlugin(**slack_configs) - endpoints.slack.create_endpoints(slack) - bot.load_plugin(slack) - - admin_configs = dict(**slack_configs) - admin_token = os.environ.get("APP_ADMIN_OAUTH_TOKEN", "FAKE_ADMIN_TOKEN") - if admin_token: - admin_configs["token"] = admin_token - admin_slack = SlackPlugin(**admin_configs) - bot.load_plugin(admin_slack, name="admin_slack") - - airtable = AirtablePlugin() - endpoints.airtable.create_endpoints(airtable) - bot.load_plugin(airtable) - - api_plugin = APIPlugin() - endpoints.api.create_endpoints(api_plugin) - bot.load_plugin(api_plugin) - - # Add route to respond to AWS health check - bot.router.add_get("/health", handle_health_check) - logging.getLogger("aiohttp.access").setLevel(logging.WARNING) - - bot.start(host=HOST, port=PORT, print=logger.info) diff --git a/pybot/customLogging.py b/pybot/customLogging.py deleted file mode 100644 index 10e2aeef..00000000 --- a/pybot/customLogging.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging - - -class SlackMessageFilter(logging.Filter): - def filter(self, record): - return record.funcName != "_incoming_message" diff --git a/pybot/endpoints/__init__.py b/pybot/endpoints/__init__.py deleted file mode 100644 index bff287d7..00000000 --- a/pybot/endpoints/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from aiohttp.web_response import Response - -from . import airtable, api, slack - - -async def handle_health_check(request): - return Response(status=200) diff --git a/pybot/endpoints/airtable/__init__.py b/pybot/endpoints/airtable/__init__.py deleted file mode 100644 index b66cf71a..00000000 --- a/pybot/endpoints/airtable/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import requests - - -def create_endpoints(plugin): - requests.create_endpoints(plugin) diff --git a/pybot/endpoints/airtable/message_templates/messages.py b/pybot/endpoints/airtable/message_templates/messages.py deleted file mode 100644 index 0fb302ba..00000000 --- a/pybot/endpoints/airtable/message_templates/messages.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import List - - -def mentor_request_text( - user_id, service, skillsets, affiliation, requested_mentor_message=None -): - if not skillsets: - skillsets = "None provided" - text = ( - f"User <@{user_id}> has requested a mentor for {service}\n\n" - f"Requested Skillset(s): {skillsets.replace(',', ', ')}\n\n" - f"Requestor Affiliation: {affiliation}" - ) - - if requested_mentor_message: - text += requested_mentor_message - - return text - - -def claim_mentee_attachment(record: str) -> List[dict]: - return [ - { - "text": "", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "claim_mentee", - "attachment_type": "default", - "actions": [ - { - "name": f"{record}", - "text": "Claim Mentee", - "type": "button", - "style": "primary", - "value": "mentee_claimed", - } - ], - } - ] diff --git a/pybot/endpoints/airtable/requests.py b/pybot/endpoints/airtable/requests.py deleted file mode 100644 index 47f94d23..00000000 --- a/pybot/endpoints/airtable/requests.py +++ /dev/null @@ -1,50 +0,0 @@ -import asyncio -import logging - -from sirbot import SirBot - -from pybot.endpoints.airtable.utils import ( - _create_messages, - _get_matching_skillset_mentors, - _get_requested_mentor, - _post_messages, - _slack_user_id_from_email, -) - -logger = logging.getLogger(__name__) - - -def create_endpoints(plugin): - plugin.on_request("mentor_request", mentor_request) - - -async def mentor_request(request: dict, app: SirBot) -> None: - """ - Endpoint that receives the zapier POST when a new Mentor Request comes in. - - Queries Airtable to find mentors matching the requested skillsets and posts a message - in the Mentor slack channel. - """ - slack = app.plugins["slack"].api - airtable = app.plugins["airtable"].api - - id_fallback = f" [couldn't find user - email provided: {request['email']} ]" - slack_id = await _slack_user_id_from_email( - request["email"], slack, fallback=id_fallback - ) - - futures = [ - airtable.get_name_from_record_id("Services", request["service"]), - _get_requested_mentor(request.get("requested_mentor"), slack, airtable), - _get_matching_skillset_mentors(request.get("skillsets"), slack, airtable), - ] - - service_translation, requested_mentor_message, mentors = await asyncio.gather( - *futures - ) - - first_message, *children = _create_messages( - mentors, request, requested_mentor_message, service_translation, slack_id - ) - - await _post_messages(first_message, children, app) diff --git a/pybot/endpoints/airtable/utils.py b/pybot/endpoints/airtable/utils.py deleted file mode 100644 index c0801f9e..00000000 --- a/pybot/endpoints/airtable/utils.py +++ /dev/null @@ -1,97 +0,0 @@ -from typing import List, Optional, Tuple - -from sirbot import SirBot -from slack import ROOT_URL, methods -from slack.events import Message -from slack.exceptions import SlackAPIError -from slack.io.aiohttp import SlackAPI - -from pybot.endpoints.slack.utils import MENTOR_CHANNEL -from pybot.plugins.airtable.api import AirtableAPI - -from .message_templates.messages import claim_mentee_attachment, mentor_request_text - - -async def _get_requested_mentor( - requested_mentor: Optional[str], slack: SlackAPI, airtable: AirtableAPI -) -> Optional[str]: - try: - if not requested_mentor: - return None - mentor = await airtable.get_row_from_record_id("Mentors", requested_mentor) - email = mentor["Email"] - slack_user_id = await _slack_user_id_from_email(email, slack) - return f" Requested mentor: <@{slack_user_id}>" - except SlackAPIError: - return None - - -async def _slack_user_id_from_email( - email: str, slack: SlackAPI, fallback: Optional[str] = None -) -> str: - try: - response = await slack.query( - url=ROOT_URL + "users.lookupByEmail", data={"email": email} - ) - return response["user"]["id"] - except SlackAPIError: - return fallback or "Slack User" - - -async def _get_matching_skillset_mentors( - skillsets: str, slack: SlackAPI, airtable: AirtableAPI -) -> List[str]: - if not skillsets: - return ["No skillset Given"] - mentors = await airtable.find_mentors_with_matching_skillsets(skillsets) - mentor_ids = [ - await _slack_user_id_from_email( - mentor["Email"], slack, fallback=mentor["Slack Name"] - ) - for mentor in mentors - ] - return [f"<@{mentor}>" for mentor in mentor_ids] - - -def _create_messages( - mentors: List[str], - request: dict, - requested_mentor_message: str, - service_translation: str, - slack_id: str, -) -> Tuple[dict, dict, dict]: - first_message = { - "text": mentor_request_text( - slack_id, - service_translation, - request.get("skillsets", None), - request.get("affiliation", "None Provided"), - requested_mentor_message, - ), - "attachments": claim_mentee_attachment(request["record"]), - "channel": MENTOR_CHANNEL, - } - - details_message = { - "text": f"Additional details: {request.get('details', 'None Given')}", - "channel": MENTOR_CHANNEL, - } - - matching_mentors_message = { - "text": "Mentors matching all or some of the requested skillsets: " - + " ".join(mentors), - "channel": MENTOR_CHANNEL, - } - - return first_message, details_message, matching_mentors_message - - -async def _post_messages(parent: Message, children: List[Message], app: SirBot) -> None: - response = await app.plugins["slack"].api.query( - url=methods.CHAT_POST_MESSAGE, data=parent - ) - timestamp = response["ts"] - - for child in children: - child["thread_ts"] = timestamp - await app.plugins["slack"].api.query(url=methods.CHAT_POST_MESSAGE, data=child) diff --git a/pybot/endpoints/api/__init__.py b/pybot/endpoints/api/__init__.py deleted file mode 100644 index 27b5aec9..00000000 --- a/pybot/endpoints/api/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import slack_api - - -def create_endpoints(plugin): - slack_api.create_endpoints(plugin) diff --git a/pybot/endpoints/api/slack_api.py b/pybot/endpoints/api/slack_api.py deleted file mode 100644 index 3a15bb7a..00000000 --- a/pybot/endpoints/api/slack_api.py +++ /dev/null @@ -1,70 +0,0 @@ -import logging - -from sirbot import SirBot -from slack import ROOT_URL -from slack.exceptions import SlackAPIError - -from pybot.endpoints.api.utils import ( - _slack_info_from_email, - handle_slack_invite_error, - production_only, -) -from pybot.plugins import APIPlugin -from pybot.plugins.api.request import SlackApiRequest - -logger = logging.getLogger(__name__) - - -def create_endpoints(plugin: APIPlugin): - plugin.on_get("verify", verify, wait=True) - plugin.on_get("invite", invite, wait=True) - - -async def verify(request: SlackApiRequest, app: SirBot) -> dict: - """ - Verifies whether a user exists in the configured slack group with - the given email - - :return: The user's slack id and displayName if they exist - """ - slack = app.plugins["slack"].api - email = request.query["email"] - - user = await _slack_info_from_email(email, slack) - if user: - return {"exists": True, "id": user["id"], "displayName": user["name"]} - return {"exists": False} - - -@production_only -async def invite(request: SlackApiRequest, app: SirBot): - """ - Pulls an email out of the querystring and sends it an invite - to the slack team - - :return: The request response from slack - """ - - admin_slack = app.plugins["admin_slack"].api - slack = app.plugins["slack"].api - body = await request.json() - - if "email" not in body: - return {"error": "Must contain `email` JSON value"} - email = body["email"] - - try: - response = await admin_slack.query( - url=ROOT_URL + "users.admin.invite", data={"email": email} - ) - return response - - except SlackAPIError as e: - logger.info("Slack invite resulted in SlackAPIError: " + e.error) - await handle_slack_invite_error(email, e, slack) - return e.data - - except Exception as e: - logger.exception(e) - await handle_slack_invite_error(email, e, slack) - return e diff --git a/pybot/endpoints/api/utils.py b/pybot/endpoints/api/utils.py deleted file mode 100644 index 973fcbb5..00000000 --- a/pybot/endpoints/api/utils.py +++ /dev/null @@ -1,105 +0,0 @@ -import logging -from typing import Optional - -from slack import ROOT_URL -from slack.exceptions import SlackAPIError -from slack.io.abc import SlackAPI -from slack.methods import Methods - -from pybot.endpoints.slack.utils import OPS_CHANNEL, PYBOT_ENV -from pybot.endpoints.slack.utils.action_messages import ( - TICKET_OPTIONS, - not_claimed_attachment, -) -from pybot.plugins.api.request import SlackApiRequest - -logger = logging.getLogger(__name__) - - -async def _slack_info_from_email( - email: str, slack: SlackAPI, fallback: Optional[dict] = None -) -> dict: - try: - response = await slack.query( - url=ROOT_URL + "users.lookupByEmail", data={"email": email} - ) - return response["user"] - except SlackAPIError: - return fallback - - -def invite_failure_attachments(email: str, error: str) -> list: - attachments = [ - { - "text": "", - "callback_id": "ticket_status", - "response_type": "in_channel", - "fallback": "", - "fields": [ - {"title": "Email", "value": f"{email}", "short": True}, - {"title": "Error", "value": f"{error}", "short": True}, - ], - "actions": [ - { - "name": "status", - "text": "Current Status", - "type": "select", - "selected_options": [ - {"text": "Not Started", "value": "notStarted"} - ], - "options": [ - {"text": text, "value": value} - for value, text in TICKET_OPTIONS.items() - ], - } - ], - }, - not_claimed_attachment(), - ] - return attachments - - -async def handle_slack_invite_error(email, error, slack): - if error.error == "already_invited": - return error.data - - attachments = invite_failure_attachments(email, error) - - if error.error == "already_in_team": - slack_user = await _slack_info_from_email(email, slack) - attachments[0]["fields"].append( - { - "title": "Slack Username", - "value": f"<@{slack_user['id']}>", - "short": True, - } - ) - - response = { - "channel": OPS_CHANNEL, - "attachments": attachments, - "text": "User Slack Invite Error", - } - - return await slack.query(Methods.CHAT_POST_MESSAGE, response) - - -def production_only(func): - """ - Decorator for functions that shouldn't be called unless in - production environment. - - Used to avoid doing things like sending slack workspace invites to - staging environments. - """ - - async def not_prod(request: SlackApiRequest, app): - logger.info( - f"Received request on staging to {request.request.raw_path}. Returning 200" - ) - return {"ok": True, "details": "Development environment, returning 200"} - - if PYBOT_ENV != "PRODUCTION": - return not_prod - - return func diff --git a/pybot/endpoints/slack/__init__.py b/pybot/endpoints/slack/__init__.py deleted file mode 100644 index c616836a..00000000 --- a/pybot/endpoints/slack/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from . import actions, commands, events, messages - - -def create_endpoints(plugin): - events.create_endpoints(plugin) - actions.create_endpoints(plugin) - commands.create_endpoints(plugin) - messages.create_endpoints(plugin) diff --git a/pybot/endpoints/slack/actions/__init__.py b/pybot/endpoints/slack/actions/__init__.py deleted file mode 100644 index eaf75854..00000000 --- a/pybot/endpoints/slack/actions/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -from sirbot.plugins.slack import SlackPlugin - -from .general_actions import claimed, delete_message, reset_claim -from .mentor_request import ( - add_skillset, - claim_mentee, - clear_mentor, - clear_skillsets, - mentor_details_submit, - mentor_request_submit, - open_details_dialog, - set_group, - set_requested_mentor, - set_requested_service, -) -from .mentor_volunteer import ( - add_volunteer_skillset, - clear_volunteer_skillsets, - submit_mentor_volunteer, -) -from .new_member import ( - member_greeted, - member_messaged, - open_suggestion, - post_suggestion, - reset_greet, - reset_message, - resource_buttons, -) -from .report_message import open_report_dialog, send_report - - -def create_endpoints(plugin: SlackPlugin): - # simple actions that can be used in multiple scenarios - plugin.on_action("claimed", claimed, name="claimed", wait=False) - plugin.on_action("claimed", reset_claim, name="reset_claim", wait=False) - plugin.on_block("submission", delete_message, action_id="cancel_btn", wait=False) - - # new member interactive actions - plugin.on_action("resource_buttons", resource_buttons, wait=False) - plugin.on_action("greeted", member_greeted, name="greeted", wait=False) - plugin.on_action("greeted", reset_greet, name="reset_greet", wait=False) - plugin.on_action("messaged", member_messaged, name="messaged", wait=False) - plugin.on_action("messaged", reset_message, name="reset_message", wait=False) - plugin.on_action("suggestion", open_suggestion, wait=False) - plugin.on_action("suggestion_modal", post_suggestion, wait=False) - - # reporting related interactive actions - plugin.on_action("report_message", open_report_dialog, wait=False) - plugin.on_action("report_dialog", send_report, wait=False) - - # mentorship related interactive actions - plugin.on_block( - "mentor_service", - set_requested_service, - wait=False, - action_id="mentor_service_select", - ) - plugin.on_block("skillset", add_skillset, action_id="skillset_select", wait=False) - plugin.on_block( - "clear_skillsets", clear_skillsets, action_id="clear_skillsets_btn", wait=False - ) - plugin.on_block( - "mentor", set_requested_mentor, action_id="mentor_select", wait=False - ) - plugin.on_block( - "comments", open_details_dialog, action_id="comments_btn", wait=False - ) - plugin.on_block("mentor_details_submit", mentor_details_submit, wait=False) - plugin.on_block( - "affiliation", set_group, action_id="affiliation_select", wait=False - ) - plugin.on_block( - "submission", mentor_request_submit, action_id="submit_mentor_btn", wait=False - ) - - # mentor volunteer actions - plugin.on_block("volunteer_skillset", add_volunteer_skillset, wait=False) - plugin.on_block("clear_volunteer_skillsets", clear_volunteer_skillsets, wait=False) - plugin.on_block( - "submission", - submit_mentor_volunteer, - action_id="submit_mentor_volunteer_btn", - wait=False, - ) - - # mentorship claims - plugin.on_action("claim_mentee", claim_mentee, wait=False) - plugin.on_action("reset_claim_mentee", claim_mentee, wait=False) diff --git a/pybot/endpoints/slack/actions/general_actions.py b/pybot/endpoints/slack/actions/general_actions.py deleted file mode 100644 index 878e6527..00000000 --- a/pybot/endpoints/slack/actions/general_actions.py +++ /dev/null @@ -1,52 +0,0 @@ -from sirbot import SirBot -from slack import methods -from slack.actions import Action - -from pybot.endpoints.slack.utils.action_messages import ( - base_response, - claimed_attachment, - not_claimed_attachment, -) - - -async def claimed(action: Action, app: SirBot): - """ - Provides basic "claim" functionality for use-cases that don't have any other effects. - - Simply updates the button to allow resets and displays the user and time it was clicked. - """ - response = base_response(action) - user_id = action["user"]["id"] - - attachments = action["original_message"]["attachments"] - - for index, attachment in enumerate(attachments): - if "callback_id" in attachment and attachment["callback_id"] == "claimed": - attachments[index] = claimed_attachment(user_id) - response["attachments"] = attachments - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def reset_claim(action: Action, app: SirBot): - """ - Provides basic "unclaim" functionality for use-cases that don't have any other effects. - - Updates the button back to its initial state - """ - response = base_response(action) - - attachments = action["original_message"]["attachments"] - for index, attachment in enumerate(attachments): - if "callback_id" in attachment and attachment["callback_id"] == "claimed": - attachments[index] = not_claimed_attachment() - - response["attachments"] = attachments - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def delete_message(action: Action, app: SirBot): - slack = app.plugins["slack"].api - params = {"ts": action["message"]["ts"], "channel": action["channel"]["id"]} - - await slack.query(methods.CHAT_DELETE, params) diff --git a/pybot/endpoints/slack/actions/mentor_request.py b/pybot/endpoints/slack/actions/mentor_request.py deleted file mode 100644 index de2e757c..00000000 --- a/pybot/endpoints/slack/actions/mentor_request.py +++ /dev/null @@ -1,145 +0,0 @@ -import json -import logging - -from sirbot import SirBot -from slack import methods -from slack.actions import Action - -from pybot.endpoints.slack.message_templates.mentor_request import ( - MentorRequest, - MentorRequestClaim, -) -from pybot.endpoints.slack.utils.action_messages import mentor_details_dialog - -logger = logging.getLogger(__name__) - - -async def mentor_request_submit(action: Action, app: SirBot): - slack = app.plugins["slack"].api - airtable = app.plugins["airtable"].api - request = MentorRequest(action) - - if not request.validate_self(): - request.add_errors() - await request.update_message(slack) - return - - username = action["user"]["name"] - user_info = await slack.query(methods.USERS_INFO, {"user": action["user"]["id"]}) - email = user_info["user"]["profile"]["email"] - - airtable_response = await request.submit_request(username, email, airtable) - - if "error" in airtable_response: - await request.submission_error(airtable_response, slack) - else: - await request.submission_complete(slack) - - -async def mentor_details_submit(action: Action, app: SirBot): - slack = app.plugins["slack"].api - request = MentorRequest(action) - - state = json.loads(action["state"]) - channel = state["channel"] - ts = state["ts"] - search = {"inclusive": True, "channel": channel, "oldest": ts, "latest": ts} - - history = await slack.query(methods.CONVERSATIONS_HISTORY, search) - request["message"] = history["messages"][0] - request.details = action["submission"]["details"] - - await request.update_message(slack) - - -async def open_details_dialog(action: Action, app: SirBot): - request = MentorRequest(action) - cur_details = request.details - trigger_id = action["trigger_id"] - response = { - "trigger_id": trigger_id, - "dialog": mentor_details_dialog(action, cur_details), - } - await app.plugins["slack"].api.query(methods.DIALOG_OPEN, response) - - -async def clear_skillsets(action: Action, app: SirBot): - request = MentorRequest(action) - request.clear_skillsets() - - slack = app.plugins["slack"].api - await request.update_message(slack) - - -async def clear_mentor(action: Action, app: SirBot): - slack = app.plugins["slack"].api - - request = MentorRequest(action) - request.mentor = "" - - await request.update_message(slack) - - -async def set_group(action: Action, app: SirBot): - request = MentorRequest(action) - request.affiliation = request.selected_option - - slack = app.plugins["slack"].api - await request.update_message(slack) - - -async def set_requested_service(action: Action, app: SirBot): - request = MentorRequest(action) - - request.service = request.selected_option - - slack = app.plugins["slack"].api - await request.update_message(slack) - - -async def set_requested_mentor(action: Action, app: SirBot): - request = MentorRequest(action) - request.mentor = request.selected_option - - slack = app.plugins["slack"].api - await request.update_message(slack) - - -async def add_skillset(action: Action, app: SirBot): - request = MentorRequest(action) - selected_skill = request.selected_option - request.add_skillset(selected_skill["value"]) - - slack = app.plugins["slack"].api - await request.update_message(slack) - - -async def claim_mentee(action: Action, app: SirBot): - """ - Called when a mentor clicks on the button to claim a mentor request. - - Attempts to update airtable with the new request status and updates the claim - button allowing it to be reset if needed. - """ - try: - slack = app.plugins["slack"].api - airtable = app.plugins["airtable"].api - - event = MentorRequestClaim(action, slack, airtable) - if event.is_claim(): - user_info = await slack.query(methods.USERS_INFO, {"user": event.clicker}) - clicker_email = user_info["user"]["profile"]["email"] - - mentor_records = await airtable.find_records( - table_name="Mentors", field="Email", value=clicker_email - ) - mentor_id = mentor_records[0]["id"] if mentor_records else False - - await event.claim_request(mentor_id) - else: - await event.unclaim_request() - - await event.update_message() - - except Exception as ex: - logger.exception("Exception while updating claim", ex) diff --git a/pybot/endpoints/slack/actions/mentor_volunteer.py b/pybot/endpoints/slack/actions/mentor_volunteer.py deleted file mode 100644 index 5f79fcec..00000000 --- a/pybot/endpoints/slack/actions/mentor_volunteer.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging - -from sirbot import SirBot -from slack import methods -from slack.actions import Action -from slack.exceptions import SlackAPIError - -from pybot.endpoints.slack.message_templates.mentor_volunteer import MentorVolunteer -from pybot.endpoints.slack.utils import MENTOR_CHANNEL - -logger = logging.getLogger(__name__) - - -async def add_volunteer_skillset(action: Action, app: SirBot) -> None: - slack = app.plugins["slack"].api - - request = MentorVolunteer(action) - - selected_skill = request.selected_option - request.add_skillset(selected_skill["value"]) - await request.update_message(slack) - - -async def clear_volunteer_skillsets(action: Action, app: SirBot) -> None: - slack = app.plugins["slack"].api - - request = MentorVolunteer(action) - - request.clear_skillsets() - await request.update_message(slack) - - -async def submit_mentor_volunteer(action: Action, app: SirBot) -> None: - slack = app.plugins["slack"].api - admin_slack = app.plugins["admin_slack"].api - airtable = app.plugins["airtable"].api - - request = MentorVolunteer(action) - - if not request.validate_self(): - request.add_errors() - await request.update_message(slack) - return - - user_id = action["user"]["id"] - user_info = await slack.query(methods.USERS_INFO, {"user": user_id}) - airtable_fields = await build_airtable_fields(action, request, user_info) - - airtable_response = await airtable.add_record( - "Mentors", {"fields": airtable_fields} - ) - - if "error" in airtable_response: - request.airtable_error(airtable_response) - else: - - try: - await admin_slack.query( - methods.CONVERSATIONS_INVITE, - {"channel": MENTOR_CHANNEL, "users": [user_id]}, - ) - except SlackAPIError as error: - logger.debug("Error during mentor channel invite %s", error.data["errors"]) - - request.on_submit_success() - - await request.update_message(slack) - - -async def build_airtable_fields(action, request, user_info): - username = action["user"]["name"] - email = user_info["user"]["profile"]["email"] - name = user_info["user"]["real_name"] - airtable_fields = { - "Slack Name": username, - "Full Name": name, - "Skillsets": request.skillsets[1:], # hack to filter out empty first option - "Email": email, - } - return airtable_fields diff --git a/pybot/endpoints/slack/actions/new_member.py b/pybot/endpoints/slack/actions/new_member.py deleted file mode 100644 index 0c0c10cc..00000000 --- a/pybot/endpoints/slack/actions/new_member.py +++ /dev/null @@ -1,97 +0,0 @@ -from sirbot import SirBot -from slack import methods -from slack.actions import Action - -from pybot.endpoints.slack.utils import COMMUNITY_CHANNEL -from pybot.endpoints.slack.utils.action_messages import ( - HELP_MENU_RESPONSES, - base_response, - direct_messaged_attachment, - greeted_attachment, - new_suggestion_text, - not_direct_messaged_attachment, - not_greeted_attachment, - reset_greet_message, - suggestion_dialog, -) - - -async def resource_buttons(action: Action, app: SirBot): - """ - Edits the resource message with the clicked on resource - """ - name = action["actions"][0]["name"] - - response = base_response(action) - response["text"] = HELP_MENU_RESPONSES[name] - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def open_suggestion(action: Action, app: SirBot): - """ - Opens the suggestion modal when the user clicks on the "Are we missing something?" button - """ - trigger_id = action["trigger_id"] - response = {"trigger_id": trigger_id, "dialog": suggestion_dialog(trigger_id)} - - await app.plugins["slack"].api.query(methods.DIALOG_OPEN, response) - - -async def post_suggestion(action: Action, app: SirBot): - """ - Posts a suggestion supplied by the suggestion modal to the community channel - """ - suggesting_user = action["user"]["id"] - suggestion = action["submission"]["suggestion"] - - response = { - "text": new_suggestion_text(suggesting_user, suggestion), - "channel": COMMUNITY_CHANNEL, - } - - await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, response) - - -async def member_greeted(action: Action, app: SirBot): - """ - Called when a community member clicks the button saying they greeted the new member - """ - response = base_response(action) - user_id = action["user"]["id"] - response["attachments"] = greeted_attachment(user_id) - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def reset_greet(action: Action, app: SirBot): - """ - Resets the claim greet button back to its initial state and appends the user that hit reset and the time - """ - response = base_response(action) - response["attachments"] = not_greeted_attachment() - response["attachments"][0]["text"] = reset_greet_message(action["user"]["id"]) - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def member_messaged(action: Action, app: SirBot): - """ - Called when a outreach team member clicks the button saying they messaged the new member - """ - response = base_response(action) - user_id = action["user"]["id"] - response["attachments"] = direct_messaged_attachment(user_id) - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) - - -async def reset_message(action: Action, app: SirBot): - """ - Resets the claim messaged button back to its initial state and appends the user that hit reset and the time - """ - response = base_response(action) - response["attachments"] = not_direct_messaged_attachment() - response["attachments"][0]["text"] = reset_greet_message(action["user"]["id"]) - - await app.plugins["slack"].api.query(methods.CHAT_UPDATE, response) diff --git a/pybot/endpoints/slack/actions/report_message.py b/pybot/endpoints/slack/actions/report_message.py deleted file mode 100644 index 8949ad1e..00000000 --- a/pybot/endpoints/slack/actions/report_message.py +++ /dev/null @@ -1,36 +0,0 @@ -import json - -from sirbot import SirBot -from slack import methods -from slack.actions import Action - -from pybot.endpoints.slack.utils.action_messages import ( - build_report_message, - report_dialog, -) - - -async def send_report(action: Action, app: SirBot): - """ - Called when a user submits the report dialog. Pulls the original message - info from the state and posts the details to the moderators channel - """ - slack_id = action["user"]["id"] - details = action["submission"]["details"] - message_details = json.loads(action.action["state"]) - - response = build_report_message(slack_id, details, message_details) - - await app["plugins"]["slack"].api.query(methods.CHAT_POST_MESSAGE, response) - - -async def open_report_dialog(action: Action, app: SirBot): - """ - Opens the message reporting dialog for the user to provide details. - - Adds the message that they're reporting to the dialog's hidden state - to be pulled out when submitted. - """ - trigger_id = action["trigger_id"] - response = {"trigger_id": trigger_id, "dialog": report_dialog(action)} - await app.plugins["slack"].api.query(methods.DIALOG_OPEN, response) diff --git a/pybot/endpoints/slack/commands.py b/pybot/endpoints/slack/commands.py deleted file mode 100644 index 60318071..00000000 --- a/pybot/endpoints/slack/commands.py +++ /dev/null @@ -1,153 +0,0 @@ -import logging -import random - -from sirbot import SirBot -from sirbot.plugins.slack import SlackPlugin -from slack import methods -from slack.commands import Command - -from pybot.endpoints.slack.message_templates.commands import ( - mentor_request_blocks, - mentor_volunteer_blocks, - ticket_dialog, -) -from pybot.endpoints.slack.utils import MODERATOR_CHANNEL -from pybot.endpoints.slack.utils.action_messages import not_claimed_attachment -from pybot.endpoints.slack.utils.command_utils import get_slash_repeat_messages -from pybot.endpoints.slack.utils.general_utils import catch_command_slack_error -from pybot.endpoints.slack.utils.slash_lunch import LunchCommand - -logger = logging.getLogger(__name__) - - -def create_endpoints(plugin: SlackPlugin): - plugin.on_command("/lunch", slash_lunch, wait=False) - plugin.on_command("/repeat", slash_repeat, wait=False) - plugin.on_command("/report", slash_report, wait=False) - plugin.on_command("/roll", slash_roll, wait=False) - plugin.on_command("/mentor", slash_mentor, wait=False) - plugin.on_command("/mentor-volunteer", slash_mentor_volunteer, wait=False) - - -@catch_command_slack_error -async def slash_mentor(command: Command, app: SirBot): - airtable = app.plugins["airtable"].api - services = await airtable.get_all_records("Services", "Name") - skillsets = await airtable.get_all_records("Skillsets", "Name") - - blocks = mentor_request_blocks(services, skillsets) - - response = { - "text": "Mentor Request Form", - "blocks": blocks, - "channel": command["user_id"], - "as_user": True, - } - await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, response) - - -@catch_command_slack_error -async def slash_mentor_volunteer(command: Command, app: SirBot) -> None: - - response = { - "text": "Please fill up the Mentor Sign up Form here: https://op.co.de/volunteer-signup", - "channel": command["user_id"], - "as_user": True, - } - - await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, response) - - -@catch_command_slack_error -async def slash_report(command: Command, app: SirBot): - """ - Sends text supplied with the /report command to the moderators channel along - with a button to claim the issue - """ - slack_id = command["user_id"] - text = command["text"] - - slack = app["plugins"]["slack"].api - - message = f"<@{slack_id}> sent report: {text}" - - response = { - "text": message, - "channel": MODERATOR_CHANNEL, - "attachments": [not_claimed_attachment()], - } - - await slack.query(methods.CHAT_POST_MESSAGE, response) - - -@catch_command_slack_error -async def slash_lunch(command: Command, app: SirBot): - """ - Provides the user with a random restaurant in their area. - """ - logger.debug(command) - lunch = LunchCommand( - command["channel_id"], - command["user_id"], - command.get("text"), - command["user_name"], - ) - - slack = app["plugins"]["slack"].api - - request = lunch.get_yelp_request() - async with app.http_session.get(**request) as r: - r.raise_for_status() - message_params = lunch.select_random_lunch(await r.json()) - - await slack.query(methods.CHAT_POST_EPHEMERAL, message_params) - - -@catch_command_slack_error -async def slash_repeat(command: Command, app: SirBot): - logger.info(f"repeat command data incoming {command}") - channel_id = command["channel_id"] - slack_id = command["user_id"] - slack = app["plugins"]["slack"].api - - method_type, message = get_slash_repeat_messages( - slack_id, channel_id, command["text"] - ) - - await slack.query(method_type, message) - - -@catch_command_slack_error -async def slash_roll(command: Command, app: SirBot): - """ - Invoked via the command /roll XdY, where X is an integer between 1 and 10, - and y is an integer between 1 and 20. - - Parses the number of dice and the type from the command - """ - slack = app["plugins"]["slack"].api - slack_id = command["user_id"] - channel_id = command["channel_id"] - text = command["text"] - - try: - text = text.lower() - numdice, typedice = [int(num) for num in text.split("d")] - if numdice <= 0 or numdice > 10 or typedice <= 0 or typedice > 20: - raise ValueError - except ValueError: - logger.debug("invalid input to roll: %s", text) - response = dict( - user=slack_id, - channel=channel_id, - text=( - "Sorry, I didn't understand your input. " - "Should be XDYY where X is the number of dice, and YY is the number of sides" - ), - ) - return await slack.query(methods.CHAT_POST_EPHEMERAL, response) - - dice = [random.randint(1, typedice + 1) for _ in range(numdice)] - message = f"<@{slack_id}> Rolled {numdice} D{typedice}: {dice}" - response = dict(channel=channel_id, text=message) - await slack.query(methods.CHAT_POST_MESSAGE, response) diff --git a/pybot/endpoints/slack/events.py b/pybot/endpoints/slack/events.py deleted file mode 100644 index ece715ab..00000000 --- a/pybot/endpoints/slack/events.py +++ /dev/null @@ -1,45 +0,0 @@ -import asyncio -import logging - -from sirbot import SirBot -from slack.events import Event - -from pybot.endpoints.slack.utils.event_utils import ( - build_messages, - get_backend_auth_headers, - link_backend_user, - send_community_notification, - send_user_greetings, -) - -logger = logging.getLogger(__name__) - - -def create_endpoints(plugin): - plugin.on_event("team_join", team_join, wait=False) - - -async def team_join(event: Event, app: SirBot) -> None: - """ - Handler for when the Slack workspace has a new member join. - - After 30 seconds sends the new user a greeting, some resource links, and - notifies the community channel of the new member. - """ - slack_api = app.plugins["slack"].api - user_id = event["user"]["id"] - - *user_messages, community_message, outreach_team_message = build_messages(user_id) - futures = [ - send_user_greetings(user_messages, slack_api), - send_community_notification(community_message, slack_api), - send_community_notification(outreach_team_message, slack_api), - ] - - logger.info(f"New team join event: {event}") - await asyncio.sleep(30) - await asyncio.wait(futures) - - headers = await get_backend_auth_headers(app.http_session) - if headers: - await link_backend_user(user_id, headers, slack_api, app.http_session) diff --git a/pybot/endpoints/slack/message_templates/__init__.py b/pybot/endpoints/slack/message_templates/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pybot/endpoints/slack/message_templates/block_action.py b/pybot/endpoints/slack/message_templates/block_action.py deleted file mode 100644 index ac6b30bc..00000000 --- a/pybot/endpoints/slack/message_templates/block_action.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -from enum import IntEnum -from typing import Any, Coroutine, MutableMapping, Optional - -from slack import methods -from slack.actions import Action -from slack.io.abc import SlackAPI - - -class BlockAction(Action): - """ - Base class for working with Block format Slack Action events. - See https://api.slack.com/reference/messaging/blocks - and https://api.slack.com/messaging/composing/layouts - """ - - def __init__(self, raw_action: MutableMapping): - super().__init__(raw_action) - - @property - def original_message(self): - return self["message"] - - @property - def channel(self): - return self["channel"]["id"] - - @property - def blocks(self) -> list: - return self.original_message["blocks"] - - @blocks.setter - def blocks(self, value) -> None: - self.original_message["blocks"] = value - - @property - def attachments(self) -> list: - return self.original_message.get("attachments", []) - - @attachments.setter - def attachments(self, value) -> None: - self.original_message["attachments"] = value - - @property - def ts(self) -> str: - return self.original_message["ts"] - - @property - def actions(self): - return self["actions"] - - @property - def selected_option(self) -> Optional[dict]: - if "selected_option" in self.actions[0]: - return self.actions[0]["selected_option"] - return None - - def initial_option(self, index: IntEnum) -> str: - """ - Each section uses the `initial_option` key to store the latest - option selected by the user - """ - accessory = self.blocks[index]["accessory"] - if "initial_option" in accessory: - return accessory["initial_option"]["value"] - return "" - - @property - def update_params(self) -> dict: - return { - "channel": self.channel, - "ts": self.ts, - "blocks": self.blocks, - "attachments": self.attachments, - } - - def validate_self(self) -> bool: - """ - Should be overridden if action has any validation - """ - return True - - def update_message(self, slack: SlackAPI) -> Coroutine[Any, Any, dict]: - return slack.query(methods.CHAT_UPDATE, self.update_params) - - def add_errors(self): - error_attachment = { - "text": ":warning: Error - Cannot submit with current values :warning:", - "color": "danger", - } - self.attachments = [error_attachment] - - def clear_errors(self) -> None: - self.attachments = [] diff --git a/pybot/endpoints/slack/message_templates/commands.py b/pybot/endpoints/slack/message_templates/commands.py deleted file mode 100644 index 656e52a0..00000000 --- a/pybot/endpoints/slack/message_templates/commands.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import List - - -def ticket_dialog(clicker_email, text): - return { - "callback_id": "open_ticket", - "title": "Open New Ticket", - "submit_label": "Submit", - "elements": [ - { - "type": "text", - "label": "Email", - "name": "email", - "subtype": "email", - "value": clicker_email, - }, - {"type": "text", "label": "Request Type", "name": "type", "value": text}, - {"type": "textarea", "label": "Details", "name": "details"}, - ], - } - - -def mentor_request_blocks(services, skillsets): - return [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ( - "👨‍🏫 Mentor Request Form 👩‍🏫\n" - "Thank you for signing up for a 30 minute mentoring session. Please fill out the form below" - ), - }, - }, - {"type": "divider"}, - { - "type": "section", - "block_id": "mentor_service", - "text": {"type": "mrkdwn", "text": "*Service*"}, - "accessory": { - "action_id": "mentor_service_select", - "type": "static_select", - "placeholder": {"type": "plain_text", "text": "Service"}, - "options": [ - {"text": {"type": "plain_text", "text": service}, "value": service} - for service in services - ], - }, - }, - { - "type": "section", - "block_id": "skillset", - "text": {"type": "mrkdwn", "text": "*Mentor Skillsets*\n"}, - "accessory": { - "type": "static_select", - "action_id": "skillset_select", - "placeholder": {"type": "plain_text", "text": "Skillset"}, - "options": [ - { - "text": {"type": "plain_text", "text": skillset}, - "value": skillset, - } - for skillset in sorted(skillsets) - ], - }, - }, - { - "type": "section", - "block_id": "clear_skillsets", - "text": {"type": "mrkdwn", "text": "*Selected Skillsets*"}, - "accessory": { - "type": "button", - "action_id": "clear_skillsets_btn", - "text": {"type": "plain_text", "text": "Reset Skillsets"}, - "value": "reset_skillsets", - }, - }, - { - "type": "section", - "block_id": "comments", - "text": {"type": "mrkdwn", "text": "*Add comments* (required)"}, - "accessory": { - "type": "button", - "action_id": "comments_btn", - "text": {"type": "plain_text", "text": "Add details"}, - "value": "addDetails", - }, - "fields": [{"type": "plain_text", "text": " "}], - }, - { - "type": "section", - "block_id": "affiliation", - "text": { - "type": "mrkdwn", - "text": "*I certify that I am a member of the following group*", - }, - "accessory": { - "type": "static_select", - "action_id": "affiliation_select", - "placeholder": {"type": "plain_text", "text": "Military affiliation"}, - "options": [ - { - "text": {"type": "plain_text", "text": "Veteran"}, - "value": "Veteran", - }, - { - "text": {"type": "plain_text", "text": "Active Duty"}, - "value": "Active Duty", - }, - { - "text": {"type": "plain_text", "text": "Military Spouse"}, - "value": "Military Spouse", - }, - { - "text": {"type": "plain_text", "text": "Non Veteran"}, - "value": "Non Veteran", - }, - ], - }, - }, - {"type": "divider"}, - { - "type": "actions", - "block_id": "submission", - "elements": [ - { - "type": "button", - "action_id": "submit_mentor_btn", - "text": {"type": "plain_text", "text": "Submit"}, - "style": "primary", - "value": "submit", - }, - { - "type": "button", - "action_id": "cancel_btn", - "text": {"type": "plain_text", "text": "Cancel"}, - "style": "danger", - "value": "cancel", - }, - ], - }, - ] - - -def mentor_volunteer_blocks(skillsets: List[str]) -> List[dict]: - return [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ( - "Thank you for volunteering to be a mentor for the Operation Code community! If you're looking " - "for the mentor request form, please use `/mentor` instead." - ), - }, - }, - {"type": "divider"}, - { - "type": "section", - "block_id": "volunteer_skillset", - "text": { - "type": "mrkdwn", - "text": "*What area(s) are you interested in mentoring in?*", - }, - "accessory": { - "type": "static_select", - "action_id": "skillset_select", - "placeholder": {"type": "plain_text", "text": "Skillset"}, - "options": [ - { - "text": {"type": "plain_text", "text": skillset}, - "value": skillset, - } - for skillset in sorted(skillsets) - ], - }, - }, - { - "type": "section", - "block_id": "clear_volunteer_skillsets", - "text": {"type": "mrkdwn", "text": "*Selected Skillsets*"}, - "fields": [{"type": "plain_text", "text": " ", "emoji": True}], - "accessory": { - "type": "button", - "action_id": "clear_skillsets_btn", - "text": {"type": "plain_text", "text": "Reset Skillsets"}, - "value": "reset_skillsets", - }, - }, - {"type": "divider"}, - { - "type": "actions", - "block_id": "submission", - "elements": [ - { - "type": "button", - "action_id": "submit_mentor_volunteer_btn", - "text": {"type": "plain_text", "text": "Submit"}, - "style": "primary", - "value": "submit", - }, - { - "type": "button", - "action_id": "cancel_btn", - "text": {"type": "plain_text", "text": "Cancel"}, - "style": "danger", - "value": "cancel", - }, - ], - }, - ] diff --git a/pybot/endpoints/slack/message_templates/mentor_request.py b/pybot/endpoints/slack/message_templates/mentor_request.py deleted file mode 100644 index 65062864..00000000 --- a/pybot/endpoints/slack/message_templates/mentor_request.py +++ /dev/null @@ -1,273 +0,0 @@ -from enum import IntEnum -from typing import Any, Coroutine, MutableMapping, Optional - -from slack import methods -from slack.actions import Action -from slack.io.abc import SlackAPI - -from pybot.endpoints.slack.utils.action_messages import now -from pybot.plugins.airtable.api import AirtableAPI - -from .block_action import BlockAction - - -class BlockIndex(IntEnum): - SERVICE = 2 - SKILLSET = 3 - SELECTED_SKILLSETS = 4 - COMMENTS = 5 - AFFILIATION = 6 - SUBMIT = 8 - - -class MentorRequest(BlockAction): - def __init__(self, raw_action: MutableMapping): - super().__init__(raw_action) - - @property - def service(self): - return self.initial_option(BlockIndex.SERVICE) - - @service.setter - def service(self, new_service): - block = self.blocks[BlockIndex.SERVICE] - block["accessory"]["initial_option"] = new_service - if self.validate_self(): - self.clear_errors() - - @property - def skillsets(self) -> [str]: - if self.skillset_fields: - return [field["text"] for field in self.skillset_fields] - return [] - - @property - def skillset_fields(self) -> list: - return self.blocks[BlockIndex.SELECTED_SKILLSETS].get("fields", []) - - def add_skillset(self, skillset: str) -> None: - """ - Appends the new skillset to the displayed skillsets - """ - if skillset not in self.skillsets: - new_field = {"type": "plain_text", "text": skillset, "emoji": True} - self.blocks[BlockIndex.SELECTED_SKILLSETS].setdefault("fields", []).append( - new_field - ) - - @property - def details(self) -> str: - block = self.blocks[BlockIndex.COMMENTS] - if "fields" in block: - return block["fields"][0]["text"] - return "" - - @details.setter - def details(self, new_details: str) -> None: - field = {"type": "plain_text", "text": new_details} - self.blocks[BlockIndex.COMMENTS]["fields"] = [field] - - @property - def affiliation(self) -> str: - return self.initial_option(BlockIndex.AFFILIATION) - - @affiliation.setter - def affiliation(self, new_affiliation: str) -> None: - self.blocks[BlockIndex.AFFILIATION]["accessory"][ - "initial_option" - ] = new_affiliation - - if self.validate_self(): - self.clear_errors() - - def validate_self(self) -> bool: - if not self.service or not self.affiliation or not self.details: - return False - self.clear_errors() - return True - - def add_errors(self) -> None: - submit_attachment = { - "text": ":warning: Service, group certification and comments are required. :warning:", - "color": "danger", - } - self.attachments = [submit_attachment] - - async def submit_request(self, username: str, email: str, airtable: AirtableAPI): - params = {"Slack User": username, "Email": email, "Status": "Available"} - if self.skillsets: - params["Skillsets"] = self.skillsets - if self.details: - params["Additional Details"] = self.details - - service_records = await airtable.find_records("Services", "Name", self.service) - params["Service"] = [service_records[0]["id"]] - return await airtable.add_record("Mentor Request", {"fields": params}) - - def submission_error( - self, airtable_response, slack: SlackAPI - ) -> Coroutine[Any, Any, dict]: - error_attachment = { - "text": ( - f"Something went wrong.\n" - f'Error Type:{airtable_response["error"]["type"]}\n' - f'Error Message: {airtable_response["error"]["message"]}' - ), - "color": "danger", - } - self.attachments = [error_attachment] - return self.update_message(slack) - - def submission_complete(self, slack: SlackAPI) -> Coroutine[Any, Any, dict]: - done_block = { - "type": "section", - "block_id": "submission", - "text": {"type": "mrkdwn", "text": "Request Submitted Successfully!"}, - "accessory": { - "type": "button", - "action_id": "cancel_btn", - "text": {"type": "plain_text", "text": "Dismiss", "emoji": True}, - "value": "dismiss", - }, - } - - self.blocks = [done_block] - - return self.update_message(slack) - - def clear_skillsets(self) -> None: - if self.skillset_fields: - del self.blocks[BlockIndex.SELECTED_SKILLSETS]["fields"] - - def clear_errors(self) -> None: - self.attachments = [] - - -class MentorRequestClaim(Action): - def __init__( - self, raw_action: MutableMapping, slack: SlackAPI, airtable: AirtableAPI - ): - super().__init__(raw_action) - self.slack = slack - self.airtable = airtable - self.text = raw_action["original_message"]["text"] - self.attachment = raw_action["original_message"]["attachments"][0] - self.should_update = True - - @property - def trigger(self) -> dict: - return self["actions"][0] - - @property - def click_type(self) -> str: - """ - Value of the button clicked. - """ - return self.trigger["value"] - - def is_claim(self) -> bool: - """ - Returns true if the Claim button was clicked - """ - return self.click_type == "mentee_claimed" - - @property - def record(self) -> str: - """ - Airtable record ID for the mentor request - """ - return self.trigger["name"] - - @property - def clicker(self) -> str: - """ - The Slack User ID of the button clicker - """ - return self["user"]["id"] - - @property - def attachment(self) -> dict: - return self["original_message"]["attachments"][0] - - @attachment.setter - def attachment(self, value: dict) -> None: - self["original_message"]["attachments"][0] = value - - def claim_request(self, mentor_record): - """ - Updates the airtable entry with the given record. - - If record couldn't be found this object's text field is changed - to an error message to be displayed when update_message is called - """ - if mentor_record: - self.attachment = self.mentee_claimed_attachment() - else: - self.attachment[ - "text" - ] = f":warning: <@{self.clicker}>'s slack Email not found in Mentor table. :warning:" - self.should_update = False - - return self.update_airtable(mentor_record) - - def unclaim_request(self): - """ - Changes the attachment to the un-claimed version and deletes the mentor from the - Airtable record - """ - self.attachment = self.mentee_unclaimed_attachment() - return self.update_airtable("") - - async def update_airtable(self, mentor_id: Optional[str]): - if mentor_id is not None: - return await self.airtable.update_request(self.record, mentor_id) - - async def update_message(self): - """ - Builds the slack API call to update the original message - """ - response = { - "channel": self["channel"]["id"], - "ts": self["message_ts"], - "text": self.text, - "attachments": self["original_message"]["attachments"], - } - await self.slack.query(methods.CHAT_UPDATE, response) - - def mentee_claimed_attachment(self) -> dict: - return { - "text": f":100: Request claimed by <@{self.clicker}>:100:\n" - f"", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "claim_mentee", - "attachment_type": "default", - "actions": [ - { - "name": f"{self.record}", - "text": "Reset claim", - "type": "button", - "style": "danger", - "value": "reset_claim_mentee", - } - ], - } - - def mentee_unclaimed_attachment(self) -> dict: - return { - "text": f"Reset by <@{self.clicker}> at" - f" ", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "claim_mentee", - "attachment_type": "default", - "actions": [ - { - "name": f"{self.record}", - "text": "Claim Mentee", - "type": "button", - "style": "primary", - "value": "mentee_claimed", - } - ], - } diff --git a/pybot/endpoints/slack/message_templates/mentor_volunteer.py b/pybot/endpoints/slack/message_templates/mentor_volunteer.py deleted file mode 100644 index ab49b7ab..00000000 --- a/pybot/endpoints/slack/message_templates/mentor_volunteer.py +++ /dev/null @@ -1,99 +0,0 @@ -from enum import IntEnum -from typing import MutableMapping - -from pybot.endpoints.slack.utils import MENTOR_CHANNEL - -from .block_action import BlockAction - - -class VolunteerBlockIndex(IntEnum): - SKILLSET_OPTIONS = 2 - SELECTED_SKILLSETS = 3 - SUBMIT = 5 - - -class MentorVolunteer(BlockAction): - def __init__(self, raw_action: MutableMapping): - super().__init__(raw_action) - - if "original_message" not in self: - self["original_message"] = {} - - @property - def skillsets(self) -> [str]: - skillset_field = self.skillset_field_text - return skillset_field.split("\n") - - @property - def skillset_field_text(self) -> str: - return self.blocks[VolunteerBlockIndex.SELECTED_SKILLSETS]["fields"][0]["text"] - - @skillset_field_text.setter - def skillset_field_text(self, value): - self.blocks[VolunteerBlockIndex.SELECTED_SKILLSETS]["fields"][0]["text"] = value - - def add_skillset(self, skillset: str) -> None: - """ - Appends the new skillset to the displayed skillsets - """ - if skillset not in self.skillsets: - self.skillset_field_text += f"\n{skillset}" - - def clear_skillsets(self) -> None: - self.skillset_field_text = " " - - def validate_self(self): - if not self.skillsets: - return False - - self.clear_errors() - return True - - def add_errors(self) -> None: - submit_attachment = { - "text": ":warning: Please select at least one area. :warning:", - "color": "danger", - } - self.attachments = [submit_attachment] - - def airtable_error(self, airtable_response) -> None: - error_attachment = { - "text": ( - f"Something went wrong.\n" - f'Error Type:{airtable_response["error"]["type"]}\n' - f'Error Message: {airtable_response["error"]["message"]}' - ), - "color": "danger", - } - self.attachments = [error_attachment] - - def on_submit_success(self): - done_blocks = [ - {"type": "section", "text": {"type": "mrkdwn", "text": success_message}}, - { - "type": "actions", - "block_id": "submission", - "elements": [ - { - "type": "button", - "action_id": "cancel_btn", - "text": {"type": "plain_text", "text": "Dismiss"}, - "value": "dismiss", - } - ], - }, - ] - self.blocks = done_blocks - - -success_message = ( - "Thank you for signing up to be a mentor for Operation Code! You should have been automatically " - f"added to the <#{MENTOR_CHANNEL}|mentors-internal> channel. There is a bot that posts in that " - "channel when someone signs up for a 30 minute session with a mentor. If the skillsets they request " - "match the ones you listed when you signed up, you'll be notified in the thread. Click the green " - "button to claim them and reach out via DM to schedule a slack call. There are also a few pinned " - f"items in that channel that may be helpful. If you have any questions, please DM <@Raz0r|Raz0r>.\n\n" - "We don't currently have a formal long term mentorship program, but if you feel like continuing to " - "keep in contact with any members you speak to, that's perfectly fine.\n\n" - "Thank you for signing up!" -) diff --git a/pybot/endpoints/slack/message_templates/tech.py b/pybot/endpoints/slack/message_templates/tech.py deleted file mode 100644 index 74f8342c..00000000 --- a/pybot/endpoints/slack/message_templates/tech.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging -import re -from datetime import datetime, timedelta -from random import choice, random -from typing import Dict, Generator, List, Pattern - -logger = logging.getLogger(__name__) - - -class TechTermsGrabber: - # shared across all instances - TERM_URL = ( - "https://raw.githubusercontent.com/togakangaroo/tech-terms/master/terms.org" - ) - LAST_UPDATE = datetime(2012, 1, 1, 1, 1) - HOURS_BEFORE_REFRESH = 3 - - def __init__(self, app): - self.app = app - - def get_terms(self): - if ( - datetime.now() - timedelta(hours=self.HOURS_BEFORE_REFRESH) - ) > self.LAST_UPDATE: - self.TERMS = self._update_terms() - return self.TERMS - - async def _update_terms(self) -> Dict[str, list]: - two_col_org_row: Pattern[str] = self._compile_regex_from_parts() - - content = await self._grab_data_from_github() - lines: List[str] = content.splitlines() - - return { - x["term"].lower(): f'{x["term"]} is {x["definition"]}' - for x in self._filter_matches(lines, two_col_org_row) - } - - async def _grab_data_from_github(self) -> str: - async with self.app.http_session.get(self.TERM_URL) as r: - r.raise_for_status() - return await r.text(encoding="utf-8") - - def _compile_regex_from_parts(self) -> Pattern[str]: - n_spaces_pipe_n_spaces = "\\s*\\|\\s*" - non_greedy_group_of_chars = ".*?" - regex_string = ( - f"^{n_spaces_pipe_n_spaces}(?P{non_greedy_group_of_chars})" - f"{n_spaces_pipe_n_spaces}(?P{non_greedy_group_of_chars}){n_spaces_pipe_n_spaces}$" - ) - - return re.compile(regex_string) - - def _filter_matches( - self, lines: List[str], two_col_org_row: Pattern[str] - ) -> Generator[dict, None, None]: - for line in lines: - match = two_col_org_row.match(line).groupdict() - if match.get("term") and match.get("definition"): - yield match - - -class TechTerms: - # shared across all instances - TERMS = {} - ADD_GITHUB_CHANCE = 0.25 - - def __init__(self, channel: str, user: str, input_text: str, app): - - self.channel_id = channel - self.user_id = user - self.input_text = self.remove_tech(input_text) - self.app = app - self.response_params = None - - def remove_tech(self, initial_input): - return initial_input.split("!tech", 1)[1] - - async def grab_values(self) -> dict: - if not self.input_text: - return {"message": {"text": self._help_text(), "channel": self.channel_id}} - - else: - if not self.response_params: - await self._parse_input() - - if self.input_text: - return {"message": self._grab_term(term=self.input_text)} - - return {"message": self._grab_term()} - - async def _parse_input(self) -> None: - grabber = TechTermsGrabber(self.app) - self.TERMS = await grabber.get_terms() - - def _help_text(self): - return ( - "Use this to find descriptions of common and useful tech terms. Examples:\n" - + '"!tech Java" or "!tech prolog"' - + self._source_text() - ) - - def _source_text(self): - return ( - "\nTech Terms source: " - ) - - def _convert_key_to_dict(self, key: str, random_val: bool = False) -> dict: - return {"term": key, "random": random_val, "definition": f"{self.TERMS[key]}"} - - def _grab_term(self, term=None): - if term and self.TERMS.get(term.lower().strip()): - term_key: str = term.lower().strip() - return self._build_response_text(self._convert_key_to_dict(term_key)) - - return self._build_response_text(self._random_term()) - - def _build_response_text(self, term: dict) -> dict: - return {"channel": self.channel_id, "text": self._serialize_term(term)} - - def _random_term(self) -> dict: - item = choice(list(self.TERMS.keys())) - return self._convert_key_to_dict(item, random_val=True) - - def _serialize_term(self, term: Dict[str, str]) -> str: - random_text = "Selected random term:\n" - addnl = self._source_text() if random() < self.ADD_GITHUB_CHANCE else "" - - return f'{random_text if term["random"] else ""} {term["definition"]}{addnl}' diff --git a/pybot/endpoints/slack/messages.py b/pybot/endpoints/slack/messages.py deleted file mode 100644 index 04a3a1e5..00000000 --- a/pybot/endpoints/slack/messages.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging - -from sirbot import SirBot -from slack import methods -from slack.events import Message - -from .message_templates.tech import TechTerms -from .utils import BOT_URL - -logger = logging.getLogger(__name__) - - -def create_endpoints(plugin): - plugin.on_message(r".*", message_changed, subtype="message_changed") - plugin.on_message(r".*", message_deleted, subtype="message_deleted") - plugin.on_message(r".*\!tech", tech_tips) - plugin.on_message(r".*\<\!here\>", here_bad) - plugin.on_message(r".*\<\!channel\>", here_bad) - plugin.on_message(r".*\!pybot", advertise_pybot) - - -def not_bot_message(event: Message): - return ( - "message" not in event - or "subtype" not in event["message"] - or event["message"]["subtype"] != "bot_message" - ) - - -def not_bot_delete(event: Message): - return "previous_message" in event and "bot_id" not in event["previous_message"] - - -async def advertise_pybot(event: Message, app: SirBot): - response = dict( - channel=event["channel"], - text=f"OC-Community-Bot is a community led project\n <{BOT_URL}|source> ", - ) - - await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, data=response) - - -async def here_bad(event: Message, app: SirBot) -> None: - if "channel_type" in event and event["channel_type"] != "im": - user = event.get("user") - user_id = f"<@{user}>" if user else "Hey you" - await app.plugins["slack"].api.query( - methods.CHAT_POST_MESSAGE, - data=dict( - channel=event["channel"], - text=f"{user_id} - this had better be important!", - ), - ) - - -async def tech_tips(event: Message, app: SirBot): - if not_bot_message(event): - logger.info(f"tech tips logging: {event}") - try: - tech_terms = await TechTerms( - event["channel"], event["user"], event.get("text"), app - ).grab_values() - await app.plugins["slack"].api.query( - methods.CHAT_POST_MESSAGE, tech_terms["message"] - ) - - except Exception: - logger.debug(f"Exception thrown while logging message_changed {event}") - - -async def message_changed(event: Message, app: SirBot): - """ - Logs all message edits not made by a bot. - """ - try: - # need to check for bot_delete as deletes with replies that - # result in a "tombstone" also send as edits - if not_bot_message(event) and not_bot_delete(event): - logger.info( - f'CHANGE_LOGGING: edited: {event["ts"]} for user: {event["previous_message"]["user"]}\n{event}' - ) - except ValueError as e: - logger.debug( - f"Exception thrown while logging message_changed. Event: {event} || Error: {e}" - ) - - -async def message_deleted(event: Message, app: SirBot): - """ - Logs all message deletions not made by a bot. - """ - try: - if not_bot_delete(event): - logger.info(f'CHANGE_LOGGING: deleted: {event["ts"]}\nEvent: {event}') - except ValueError as e: - logger.debug( - f"Exception thrown while logging message_deleted. Event: {event} || Error: {e}" - ) diff --git a/pybot/endpoints/slack/utils/__init__.py b/pybot/endpoints/slack/utils/__init__.py deleted file mode 100644 index 69e00885..00000000 --- a/pybot/endpoints/slack/utils/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import os - -BOT_USER_OAUTH_ACCESS_TOKEN = os.environ.get("BOT_USER_OAUTH_ACCESS_TOKEN") -BOT_OAUTH_TOKEN = os.environ.get("BOT_OAUTH_TOKEN") -BOT_OATH_TOKEN = os.environ.get("BOT_OATH_TOKEN") -MENTOR_CHANNEL = os.environ.get("MENTOR_CHANNEL", "mentors-internal") -COMMUNITY_CHANNEL = os.environ.get("COMMUNITY_CHANNEL", "greetings") -MODERATOR_CHANNEL = os.environ.get("MODERATOR_CHANNEL", "moderators") -OPS_CHANNEL = os.environ.get("OPS_CHANNEL", "oc-ops") -SLACK_BOT_USER_ID = os.environ.get("SLACK_BOT_USER_ID", "ABC123") -SLACK_BOT_ID = os.environ.get("SLACK_BOT_ID", "ABC123") -YELP_TOKEN = os.environ.get("YELP_TOKEN", "token") -PORT = os.environ.get("SIRBOT_PORT", 5000) -HOST = os.environ.get("SIRBOT_ADDR", "0.0.0.0") -PYBOT_ENV = os.environ.get("PYBOT_ENV", "dev") -BACKEND_URL = os.environ.get("BACKEND_URL", "https://api.operationcode.org") -BACKEND_USERNAME = os.environ.get("BACKEND_USERNAME", "Pybot@test.test") -BACKEND_PASS = os.environ.get("BACKEND_PASS", "fakePassword") - -BOT_URL = "https://github.com/OperationCode/operationcode-pybot" - -slack_configs = { - "token": BOT_USER_OAUTH_ACCESS_TOKEN - or BOT_OAUTH_TOKEN - or BOT_OATH_TOKEN, # fallback for old values - "signing_secret": os.environ.get("SLACK_BOT_SIGNING_SECRET"), - "verify": os.environ.get("VERIFICATION_TOKEN"), - "bot_id": SLACK_BOT_ID, - "bot_user_id": SLACK_BOT_USER_ID, -} diff --git a/pybot/endpoints/slack/utils/action_messages.py b/pybot/endpoints/slack/utils/action_messages.py deleted file mode 100644 index 4ff97f09..00000000 --- a/pybot/endpoints/slack/utils/action_messages.py +++ /dev/null @@ -1,366 +0,0 @@ -import json -from time import time -from typing import List - -from pybot.endpoints.slack.utils import MODERATOR_CHANNEL - -TICKET_OPTIONS = { - "notStarted": "Not Started", - "inProgress": "In-progress", - "waitingOnUser": "Waiting on User", - "rejected": "Rejected", - "duplicate": "Mark as Duplicate", - "complete": "Complete", -} - - -def now(): - """ - This has to be pulled out into its own method so a mock can - be injected for testing purposes - """ - return int(time()) - - -def base_response(action): - response = { - "text": action["original_message"].get("text", None), - "channel": action["channel"]["id"], - "ts": action["message_ts"], - } - return response - - -def update_ticket_message(action, selected_value): - user = action["user"]["id"] - update_message = ( - f"<@{user}> updated status to {selected_value} at " - f"" - ) - return { - "text": update_message, - "channel": action["channel"]["id"], - "thread_ts": action["message_ts"], - } - - -def updated_ticket_status(action): - selected_option = action["actions"][0]["selected_options"][0] - selected_option["text"] = TICKET_OPTIONS[selected_option["value"]] - - updated_attachments = action["original_message"]["attachments"] - updated_attachments[0]["actions"][0]["selected_options"] = [selected_option] - response = {**base_response(action), "attachments": updated_attachments} - return response, selected_option - - -def ticket_attachments(action): - user_id = action["user"]["id"] - request_type = action["submission"]["type"] - email = action["submission"]["email"] - details = action["submission"]["details"] - attachments = [ - { - "text": "", - "callback_id": "ticket_status", - "response_type": "in_channel", - "fallback": "request details should have been here", - "fields": [ - {"title": "User", "value": f"<@{user_id}>", "short": True}, - {"title": "Email", "value": f"{email}", "short": True}, - {"title": "Request Type", "value": f"{request_type}", "short": True}, - {"title": "Details", "value": f"{details}", "short": True}, - ], - "actions": [ - { - "name": "status", - "text": "Current Status", - "type": "select", - "selected_options": [ - {"text": "Not Started", "value": "notStarted"} - ], - "options": [ - {"text": text, "value": value} - for value, text in TICKET_OPTIONS.items() - ], - } - ], - }, - not_claimed_attachment(), - ] - return attachments - - -def greeted_attachment(user_id: str) -> List[dict]: - return [ - { - "text": f":100:<@{user_id}> has greeted the new user!:100:\n" - f"", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "greeted", - "attachment_type": "default", - "actions": [ - { - "name": "reset_greet", - "text": "Reset claim", - "type": "button", - "style": "danger", - "value": "reset_greet", - } - ], - } - ] - - -def not_greeted_attachment(): - return [ - { - "text": "", - "fallback": "Someone should greet them!", - "color": "#3AA3E3", - "callback_id": "greeted", - "attachment_type": "default", - "actions": [ - { - "name": "greeted", - "text": "I will greet them!", - "type": "button", - "style": "primary", - "value": "greeted", - } - ], - } - ] - - -def direct_messaged_attachment(user_id: str) -> List[dict]: - return [ - { - "text": f":100:<@{user_id}> has DMed the new user!:100:\n" - f"", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "messaged", - "attachment_type": "default", - "actions": [ - { - "name": "reset_message", - "text": f"Reset DM", - "type": "button", - "style": "danger", - "value": "reset_message", - } - ], - } - ] - - -def not_direct_messaged_attachment(): - return [ - { - "text": "", - "fallback": "Someone should DM them!", - "color": "#3AA3E3", - "callback_id": "messaged", - "attachment_type": "default", - "actions": [ - { - "name": "messaged", - "text": "I will DM them!", - "type": "button", - "style": "primary", - "value": "messaged", - } - ], - } - ] - - -def not_claimed_attachment(): - return { - "text": "", - "fallback": "not claimed attachment", - "color": "#3AA3E3", - "callback_id": "claimed", - "attachment_type": "default", - "short": True, - "actions": [ - { - "name": "claimed", - "text": "Claim", - "type": "button", - "style": "primary", - "value": "claimed", - } - ], - } - - -def claimed_attachment(user_id): - return { - "text": f"Claimed by <@{user_id}>\n" - f"", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "claimed", - "attachment_type": "default", - "actions": [ - { - "name": "reset_claim", - "text": "Reset claim", - "type": "button", - "style": "danger", - "value": "reset_claim", - } - ], - } - - -def reset_greet_message(user_id): - return ( - f"Reset by <@{user_id}> at" - f" " - ) - - -def suggestion_dialog(trigger_id): - return { - "callback_id": "suggestion_modal", - "title": "Help topic suggestion", - "submit_label": "Submit", - "trigger_id": trigger_id, - "elements": [ - { - "type": "text", - "label": "Suggestion", - "name": "suggestion", - "placeholder": "Underwater Basket Weaving", - } - ], - } - - -def report_dialog(action): - trigger_id = action["trigger_id"] - - user = action["message"].get("user") or action["message"].get( - "username" - ) # for bots - message_data = { - "text": action["message"]["text"], - "user": user, - "channel": action["channel"], - } - return { - "callback_id": "report_dialog", - "state": json.dumps(message_data), - "title": "Report details", - "submit_label": "Submit", - "trigger_id": trigger_id, - "elements": [ - { - "type": "textarea", - "label": "Details", - "name": "details", - "placeholder": "", - "required": False, - } - ], - } - - -def build_report_message(slack_id, details, message_details): - message = f"<@{slack_id}> sent a report with details: {details}" - - attachment = [ - { - "fields": [ - { - "title": "User", - "value": f"<@{message_details['user']}>", - "short": True, - }, - { - "title": "Channel", - "value": f"<#{message_details['channel']['id']}|{message_details['channel']['name']}>", - "short": True, - }, - {"title": "Message", "value": message_details["text"], "short": False}, - ] - }, - not_claimed_attachment(), - ] - - return {"text": message, "channel": MODERATOR_CHANNEL, "attachments": attachment} - - -def mentor_details_dialog(action, cur_details): - trigger_id = action["trigger_id"] - ts = action["message"]["ts"] - - return { - "callback_id": "mentor_details_submit", - "state": json.dumps({"ts": ts, "channel": action["channel"]["id"]}), - "title": "Additional Details", - "submit_label": "Submit", - "trigger_id": trigger_id, - "elements": [ - { - "type": "textarea", - "label": "Details", - "name": "details", - "placeholder": "", - "required": False, - "value": cur_details, - } - ], - } - - -def new_suggestion_text(user_id: str, suggestion: str) -> str: - return f":exclamation:<@{user_id}> just submitted a suggestion for a help topic:exclamation:\n-- {suggestion}" - - -HELP_MENU_RESPONSES = { - "slack": "Slack is an online chatroom service that the Operation Code community uses.\n" - "It can be accessed online, via https://operation-code.slack.com/ or via\n" - "desktop or mobile apps, located at https://slack.com/downloads/. In addition to\n" - "chatting, Slack also allows us to share files, audio conference and even program\n" - "our own bots! Here are some tips to get you started:\n" - " - You can customize your notifications per channel by clicking the gear to the\n" - " left of the search box\n" - " - Join as many channels as you want via the + next to Channels in the side bar.", - "python": "Python is a widely used high-level programming language used for general-purpose programming.\n" - "It's very friendly for beginners and is great for everything from web development to \n" - "data science.\n\n" - "Here are some python resources:\n" - " Operation Code Python Room: <#C04D6M3JT|python>\n" - " Python's official site: https://www.python.org/\n" - " Learn Python The Hard Way: https://learnpythonthehardway.org/book/\n" - " Automate The Boring Stuff: https://automatetheboringstuff.com/", - "mentor": "The Operation Code mentorship program aims to pair you with an experienced developer in order to" - " further your programming or career goals. When you sign up for our mentorship program you'll fill" - " out a form with your interests. You'll then be paired up with an available mentor that best meets" - " those interests.\n\n" - "If you're interested in getting paired with a mentor, please fill out our sign up form" - " here: http://op.co.de/mentor-request.\n ", - "javascript": "Javascript is a high-level programming language used for general-purpose programming.\n" - "In recent years it has exploded in popularity and with the popular node.js runtime\n" - "environment it can run anywhere from the browser to a server.\n\n" - "Here are some javascript resources:\n Operation Code Javascript Room: <#C04CJ8H2S|javascript>\n" - " Javascript Koans: https://github.com/mrdavidlaing/javascript-koans\n" - " Eloquent Javascript: http://eloquentjavascript.net/\n" - " Node School: http://nodeschool.io/\n" - " Node University: http://node.university/courses", - "ruby": "Ruby is one of the most popular languages to learn as a beginner.\n" - "While it can be used in any situation it's most popular for it's\n" - "web framework 'Rails' which allows people to build websites quickly \n" - "and easily.\n\n" - "Here are some ruby resources:\n" - " Operation Code Ruby Room: <#C04D6GTGT|ruby>\n" - " Try Ruby Online: http://tryruby.org/\n" - " Learn Ruby The Hard Way: http://ruby.learncodethehardway.org/book\n" - " Learn To Program: http://pine.fm/LearnToProgram/\n" - " Ruby Koans: http://rubykoans.com/", -} diff --git a/pybot/endpoints/slack/utils/command_utils.py b/pybot/endpoints/slack/utils/command_utils.py deleted file mode 100644 index 96d2ca90..00000000 --- a/pybot/endpoints/slack/utils/command_utils.py +++ /dev/null @@ -1,20 +0,0 @@ -from slack import methods - -from pybot.endpoints.slack.utils.slash_repeat import repeat_items - - -def get_slash_repeat_messages(user_id, channel, text): - response_type = { - "ephemeral": methods.CHAT_POST_EPHEMERAL, - "message": methods.CHAT_POST_MESSAGE, - } - - values_dict = repeat_items(text, user_id, channel) - return response_type[values_dict["type"]], values_dict["message"] - - -def action_value(attachment): - action = attachment["actions"][0] - if "selected_options" in action: - return action["selected_options"][0]["value"] - return "" diff --git a/pybot/endpoints/slack/utils/event_messages.py b/pybot/endpoints/slack/utils/event_messages.py deleted file mode 100644 index 73e14c00..00000000 --- a/pybot/endpoints/slack/utils/event_messages.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import List - - -def team_join_initial_message(user_id: str) -> str: - return ( - f"Hi <@{user_id}>,\n\n" - "Welcome to Operation Code! I'm a bot designed to help answer questions and " - "get you on your way in our community.\n\n " - "Our goal here at Operation Code is to get veterans and their families started on the path to a career " - "in programming. We do that through providing you with scholarships, mentoring, career development " - "opportunities, conference tickets, and more!\n" - ) - - -def second_team_join_message() -> str: - return ( - "Much of the provided aid requires veteran or military spouse status. Please verify your status on " - "your profile at https://operationcode.org/ if you haven't already.\n\n" - "You're currently in Slack, a chat application that serves as the hub of Operation Code. " - "If you're visiting us via your browser, Slack provides a stand alone program to make staying in " - "touch even more convenient.\n\n" - "All active Operation Code projects are located on our source control repository. " - "Our projects can be viewed on GitHub\n\n" - "Lastly, please take a moment to review our Code of Conduct." - ) - - -def external_button_attachments() -> List[dict]: - return [ - { - "text": "", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "external_buttons", - "attachment_type": "default", - "actions": [ - { - "name": "github", - "text": "GitHub", - "type": "button", - "value": "github", - "url": "https://github.com/OperationCode", - }, - { - "name": "download", - "text": "Slack Client", - "type": "button", - "value": "download", - "url": "https://slack.com/downloads", - }, - { - "name": "code_of_conduct", - "text": "Code of Conduct", - "type": "button", - "value": "code_of_conduct", - "url": "https://github.com/OperationCode/community/blob/master/code_of_conduct.md", - }, - ], - } - ] - - -def base_resources(): - return [ - { - "text": "", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "resource_buttons", - "attachment_type": "default", - "actions": [ - { - "name": "javascript", - "text": "JavaScript", - "type": "button", - "value": "javascript", - }, - { - "name": "python", - "text": "Python", - "type": "button", - "value": "python", - }, - {"name": "ruby", "text": "Ruby", "type": "button", "value": "ruby"}, - ], - }, - { - "text": "", - "fallback": "", - "color": "#3AA3E3", - "callback_id": "suggestion", - "attachment_type": "default", - "actions": [ - { - "name": "suggestion_button", - "text": "Are we missing something? Click!", - "type": "button", - "value": "suggestion_button", - } - ], - }, - ] diff --git a/pybot/endpoints/slack/utils/event_utils.py b/pybot/endpoints/slack/utils/event_utils.py deleted file mode 100644 index b028e255..00000000 --- a/pybot/endpoints/slack/utils/event_utils.py +++ /dev/null @@ -1,122 +0,0 @@ -import logging -from typing import Dict, List, Tuple - -from aiohttp import ClientSession -from slack import methods -from slack.events import Message -from slack.io.abc import SlackAPI - -from pybot.endpoints.slack.utils import ( - BACKEND_PASS, - BACKEND_URL, - BACKEND_USERNAME, - COMMUNITY_CHANNEL, -) -from pybot.endpoints.slack.utils.action_messages import ( - not_direct_messaged_attachment, - not_greeted_attachment, -) -from pybot.endpoints.slack.utils.event_messages import ( - base_resources, - external_button_attachments, - second_team_join_message, - team_join_initial_message, -) - -logger = logging.getLogger(__name__) - - -def base_user_message(user_id: str) -> Message: - message = Message() - message["channel"] = user_id - message["as_user"] = True - return message - - -def build_messages(user_id) -> Tuple[Message, Message, Message, Message, Message]: - initial_message = base_user_message(user_id) - initial_message["text"] = team_join_initial_message(user_id) - - second_message = base_user_message(user_id) - second_message["text"] = second_team_join_message() - second_message["attachments"] = external_button_attachments() - - action_menu = base_user_message(user_id) - action_menu["text"] = "We recommend the following resources." - action_menu["attachments"] = base_resources() - - community_message = Message() - community_message["text"] = f":tada: <@{user_id}> has joined! :tada:" - community_message["attachments"] = not_greeted_attachment() - community_message["channel"] = COMMUNITY_CHANNEL - - outreach_team_message = Message() - outreach_team_message["text"] = ( - f":spiral_note_pad: Outreach Team: Please reach out to <@{user_id}> via DM" - f":spiral_note_pad: " - ) - outreach_team_message["attachments"] = not_direct_messaged_attachment() - outreach_team_message["channel"] = COMMUNITY_CHANNEL - - return ( - initial_message, - second_message, - action_menu, - community_message, - outreach_team_message, - ) - - -async def send_user_greetings( - user_messages: List[Message], slack_api: SlackAPI -) -> None: - for message in user_messages: - await slack_api.query(url=methods.CHAT_POST_MESSAGE, data=message) - - -async def send_community_notification( - community_message: Message, slack_api: SlackAPI -) -> dict: - return await slack_api.query(url=methods.CHAT_POST_MESSAGE, data=community_message) - - -async def link_backend_user( - slack_id: str, - auth_header: Dict[str, str], - slack_api: SlackAPI, - session: ClientSession, -) -> None: - """ - Updates the slack user with their profile in the backend - """ - - user_info = await slack_api.query(methods.USERS_INFO, {"user": slack_id}) - email = user_info["user"]["profile"]["email"] - - async with session.patch( - f"{BACKEND_URL}/auth/profile/admin/", - headers=auth_header, - params={"email": email}, - json={"slackId": slack_id}, - ) as response: - data = await response.json() - logger.info(f"Backend response from user linking: {data}") - - -async def get_backend_auth_headers(session: ClientSession) -> Dict[str, str]: - """ - Authenticates with the OC Backend server - - :return: Authorization header containing the returned JWT - """ - async with session.post( - f"{BACKEND_URL}/auth/login/", - json={"email": BACKEND_USERNAME, "password": BACKEND_PASS}, - ) as response: - if 400 <= response.status: - logger.exception("Failed to authenticate with backend") - return {} - response.raise_for_status() - data = await response.json() - headers = {"Authorization": f"Bearer {data['token']}"} - return headers diff --git a/pybot/endpoints/slack/utils/general_utils.py b/pybot/endpoints/slack/utils/general_utils.py deleted file mode 100644 index 5399856c..00000000 --- a/pybot/endpoints/slack/utils/general_utils.py +++ /dev/null @@ -1,42 +0,0 @@ -import functools - -from sirbot import SirBot -from slack.commands import Command -from slack.exceptions import SlackAPIError -from slack.methods import Methods - - -def catch_command_slack_error(func): - """ - Decorator for wrapping/catching exceptions thrown by - the slack client and displaying an error to the user. - - Only necessary (for now) for functions that post messages to - slack channels - """ - - @functools.wraps(func) - async def handler(command: Command, app: SirBot, *args, **kwargs): - try: - await func(command, app, *args, **kwargs) - - except SlackAPIError: - channel_id = command["channel_id"] - slash_command = command["command"] - slack_id = command["user_id"] - slack = app["plugins"]["slack"] - - await slack.api.query( - Methods.CHAT_POST_EPHEMERAL, - dict( - user=slack_id, - channel=slack_id, - as_user=True, - text=( - f"Could not post result of `{slash_command}` " - f"to channel <#{channel_id}>" - ), - ), - ) - - return handler diff --git a/pybot/endpoints/slack/utils/slash_lunch.py b/pybot/endpoints/slack/utils/slash_lunch.py deleted file mode 100644 index 250ce90a..00000000 --- a/pybot/endpoints/slack/utils/slash_lunch.py +++ /dev/null @@ -1,124 +0,0 @@ -import logging -from random import randint -from typing import List - -from zipcodes import is_real - -from pybot.endpoints.slack.utils import YELP_TOKEN - -logger = logging.getLogger(__name__) - - -class LunchCommand: - DEFAULT_LUNCH_DISTANCE = 20 - MIN_LUNCH_RANGE = 1 - AUTH_HEADER = {"Authorization": f"Bearer {YELP_TOKEN}"} - - def __init__(self, channel: str, user: str, input_text: str, user_name: str): - - self.channel_id = channel - self.user_id = user - self.input_text = input_text - self.user_name = user_name - - self.lunch_api_params = self._parse_input() - - def get_yelp_request(self): - return { - "url": "https://api.yelp.com/v3/businesses/search", - "params": self.lunch_api_params, - "headers": self.AUTH_HEADER, - } - - def select_random_lunch(self, lunch_response: dict) -> dict: - location_count = len(lunch_response["businesses"]) - - selected_location = randint(0, location_count - 1) - location = lunch_response["businesses"][selected_location] - - logger.info(f"location selected for {self.user_name}: {location}") - - return self._build_response_text(location) - - # TODO: add test cases for various inputs - # TODO: allow user to set defaults - def _parse_input(self) -> dict: - if not self.input_text: - return { - "location": self._random_zip(), - "range": self._convert_to_meters(self.DEFAULT_LUNCH_DISTANCE), - "term": "lunch", - } - - else: - split_items = self.input_text.split() - zipcode = self._get_zipcode(split_items[0]) - distance = self._get_distance(split_items) - return {"location": zipcode, "range": distance, "term": "lunch"} - - def _get_distance(self, split_items: List[str]): - distance_index = min(len(split_items), 2) - 1 - - str_distance = split_items[distance_index] - distance = self._convert_max_distance(str_distance) - - if not self._within_lunch_range(distance): - distance = self.DEFAULT_LUNCH_DISTANCE - - return self._convert_to_meters(distance) - - def _build_response_text(self, loc_dict: dict) -> dict: - return { - "user": self.user_id, - "channel": self.channel_id, - "text": ( - f'The Wheel of Lunch has selected {loc_dict["name"]} ' - + f'at {" ".join(loc_dict["location"]["display_address"])}' - ), - } - - @classmethod - def _get_zipcode(cls, zipcode: str) -> int: - try: - - if is_real(zipcode): - return int(zipcode) - except TypeError: - pass - - return cls._random_zip() - - @staticmethod - def _random_zip() -> int: - """ - Because what doesn't matter is close food but good food - :return: zip_code - :rtype: str - """ - random_zip = 0 - while not is_real(str(random_zip)): - range_start = 10 ** 4 - range_end = (10 ** 5) - 1 - random_zip = randint(range_start, range_end) - - return random_zip - - def _within_lunch_range(self, input_number: int) -> bool: - return input_number <= self.DEFAULT_LUNCH_DISTANCE - - def _convert_max_distance(self, user_param: str) -> int: - - try: - distance = int(user_param) - - if distance < 0: - distance = abs(distance) - - return max(distance, self.MIN_LUNCH_RANGE) - - except ValueError: - return self.DEFAULT_LUNCH_DISTANCE - - @classmethod - def _convert_to_meters(cls, distance): - return int(distance * 1609.34) diff --git a/pybot/endpoints/slack/utils/slash_repeat.py b/pybot/endpoints/slack/utils/slash_repeat.py deleted file mode 100644 index 647fb4ab..00000000 --- a/pybot/endpoints/slack/utils/slash_repeat.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import Iterable - - -# TODO: use the github api to find the file even if location changes -def find_resources() -> dict: - return { - "link": "https://operationcode.org/resources", - "title": "A searchable database of learning resources", - "pretext": "Would you like some learning resources?", - } - - -def ask() -> dict: - return { - "link": "http://sol.gfxile.net/dontask.html", - "title": "Asking Questions", - "pretext": "You can just ask, we're all here to help", - } - - -def default_repeat_message(message_options: Iterable) -> str: - return "That is not a valid option valid options are:\n " + "".join( - [f'->\t"{key}"\n' for key in message_options] - ) - - -def modify_params(modify_options: dict) -> dict: - message = { - "channel": modify_options["channel_id"], - "attachments": [ - { - "pretext": "Text before block", - "title": "Text of link", - "title_link": "https://groove.hq/path/to/ticket/1943", - } - ], - } - - message["attachments"][0][ - "pretext" - ] = f'<@{modify_options["slack_id"]}>: {modify_options["pretext"]}' - message["attachments"][0]["title"] = modify_options["title"] - message["attachments"][0]["title_link"] = modify_options["link"] - - return message - - -def repeat_items(requested_text: str, slack_id: str, channel_id: str) -> dict: - # TODO: get better way of only showing unique values - # for keys instead of my wonky way of adding more options - messages = { - "10000": { - "link": "https://xkcd.com/1053/", - "title": "XKCD: lucky", - "pretext": "Looks like you're one of the lucky 10,000 today!", - }, - "ask": ask(), - "asking": ask(), - "ldap": { - "link": "http://large-type.com/#yes", - "title": "Is someone complaining about LDAP?", - "pretext": "What's that I hear about LDAP?", - }, - "merge": { - "link": "http://large-type.com/#WILL", - "title": "Who is that force merging to master?", - "pretext": "git push -f origin master", - }, - "firstpr": { - "link": "https://goo.gl/forms/r02wt0pBNhkxYciI3", - "title": "Get your sticker here!", - "pretext": ":firstpr:", - }, - "channels": { - "link": "https://github.com/OperationCode/operationcode_docs/blob/master/community/slack_channel_guide.md", - "title": "Channel Guide!", - "pretext": "Check out the Channel Guide!", - }, - # TODO: make this into a url call. - "resources": find_resources(), - "resource": find_resources(), - } - - modify_options = messages.get(requested_text.lower()) - - if modify_options: - modify_options["slack_id"] = slack_id - modify_options["channel_id"] = channel_id - return {"type": "message", "message": modify_params(modify_options)} - else: - return { - "type": "ephemeral", - "message": { - "channel": channel_id, - "user": slack_id, - "text": default_repeat_message(messages.keys()), - }, - } diff --git a/pybot/plugins/__init__.py b/pybot/plugins/__init__.py deleted file mode 100644 index 9833b8a4..00000000 --- a/pybot/plugins/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .airtable import AirtablePlugin -from .api import APIPlugin diff --git a/pybot/plugins/airtable/__init__.py b/pybot/plugins/airtable/__init__.py deleted file mode 100644 index 82e39ce3..00000000 --- a/pybot/plugins/airtable/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plugin import AirtablePlugin diff --git a/pybot/plugins/airtable/api.py b/pybot/plugins/airtable/api.py deleted file mode 100644 index 74db32a0..00000000 --- a/pybot/plugins/airtable/api.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging -from collections import defaultdict - -from multidict import MultiDict - -logger = logging.getLogger(__name__) - - -class AirtableAPI: - API_ROOT = "https://api.airtable.com/v0/" - record_id_to_name = defaultdict(dict) - - def __init__(self, session, api_key, base_key): - self.session = session - self.api_key = api_key - self.base_key = base_key - - async def get(self, url, **kwargs): - auth_header = {"Authorization": f"Bearer {self.api_key}"} - - async with self.session.get(url, headers=auth_header, **kwargs) as r: - return await r.json() - - async def patch(self, url, **kwargs): - auth_header = {"authorization": f"Bearer {self.api_key}"} - async with self.session.patch(url, headers=auth_header, **kwargs) as r: - r.raise_for_status() - return await r.json() - - async def post(self, url, **kwargs): - auth_header = {"authorization": f"Bearer {self.api_key}"} - async with self.session.post(url, headers=auth_header, **kwargs) as r: - return await r.json() - - async def _depaginate_records(self, url, params, offset): - records = [] - while offset: - params["offset"] = offset - response = await self.get(url, params=params) - records.extend(response["records"]) - offset = response.get("offset") - - return records - - def table_url(self, table_name, record_id=None): - url = f"{self.API_ROOT}{self.base_key}/{table_name}" - if record_id: - url += f"/{record_id}" - return url - - async def get_name_from_record_id(self, table_name: str, record_id): - if self.record_id_to_name[table_name]: - return self.record_id_to_name[table_name][record_id] - - url = self.table_url("Services") - params = {"fields[]": "Name"} - res_json = await self.get(url, params=params) - records = res_json["records"] - self.record_id_to_name[table_name] = { - record["id"]: record["fields"]["Name"] for record in records - } - return self.record_id_to_name[table_name][record_id] - - async def get_row_from_record_id(self, table_name: str, record_id: str) -> dict: - url = self.table_url(table_name, record_id) - try: - res_json = await self.get(url) - return res_json["fields"] - except Exception as ex: - logger.exception( - f"Couldn't get row from record id {record_id} in {table_name}", ex - ) - return {} - - async def get_all_records(self, table_name, field=None): - url = self.table_url(table_name) - if field: - params = {"fields[]": field} - res_json = await self.get(url, params=params) - return [record["fields"][field] for record in res_json["records"]] - else: - res_json = await self.get(url) - return res_json["records"] - - async def find_mentors_with_matching_skillsets(self, skillsets): - url = self.table_url("Mentors") - params = MultiDict( - [("fields", "Email"), ("fields", "Skillsets"), ("fields", "Slack Name")] - ) - skillsets = skillsets.split(",") - response = await self.get(url, params=params) - offset = response.get("offset") - mentors = response["records"] - - if offset: - additional_mentors = await self._depaginate_records(url, params, offset) - mentors.extend(additional_mentors) - - partial_match = [] - complete_match = [] - try: - for mentor in mentors: - if all( - skillset in mentor["fields"].get("Skillsets", []) - for skillset in skillsets - ): - complete_match.append(mentor["fields"]) - if any( - mentor["fields"] not in complete_match - and skillset in mentor["fields"].get("Skillsets", []) - for skillset in skillsets - ): - partial_match.append(mentor["fields"]) - except Exception as e: - logger.exception( - "Exception while trying to find filter mentors by skillset", e - ) - return [] - - if len(complete_match) < 5: - complete_match += partial_match - - return complete_match or partial_match - - async def find_records(self, table_name: str, field: str, value: str) -> list: - url = self.table_url(table_name) - - params = {"filterByFormula": f"FIND(LOWER('{value}'), LOWER({{{field}}}))"} - - try: - response = await self.get(url, params=params) - return response["records"] - except Exception as ex: - logger.exception( - f"Exception when attempting to get {field} from {table_name}.", ex - ) - return [] - - async def update_request(self, request_record, mentor_id): - url = self.table_url("Mentor Request", request_record) - data = {"fields": {"Mentor Assigned": [mentor_id] if mentor_id else None}} - return await self.patch(url, json=data) - - async def add_record(self, table, json): - url = self.table_url(table) - return await self.post(url, json=json) diff --git a/pybot/plugins/airtable/endpoints.py b/pybot/plugins/airtable/endpoints.py deleted file mode 100644 index 2613f252..00000000 --- a/pybot/plugins/airtable/endpoints.py +++ /dev/null @@ -1,53 +0,0 @@ -import asyncio -import logging - -from aiohttp.web_response import Response - -logger = logging.getLogger(__name__) - - -async def incoming_request(request): - airtable = request.app.plugins["airtable"] - payload = await request.json() - logger.debug("Incoming Airtable event payload: %s", payload) - - if payload["token"] != airtable.verify: - return Response(status=401) - - futures = list(_dispatch(airtable.routers["request"], payload, request.app)) - if futures: - return await _wait_and_check_result(futures) - return Response(status=200) - - -def _dispatch(router, event, app): - for handler, configuration in router.dispatch(event): - f = asyncio.ensure_future(handler(event, app)) - if configuration["wait"]: - yield f - else: - f.add_done_callback(_callback) - - -def _callback(f): - try: - f.result() - except Exception as e: - logger.exception(e) - - -async def _wait_and_check_result(futures): - dones, _ = await asyncio.wait(futures, return_when=asyncio.ALL_COMPLETED) - try: - results = [done.result() for done in dones] - except Exception as e: - logger.exception(e) - return Response(status=500) - - results = [result for result in results if isinstance(result, Response)] - if len(results) > 1: - logger.warning("Multiple web.Response for handler, returning none") - elif results: - return results[0] - - return Response(status=200) diff --git a/pybot/plugins/airtable/plugin.py b/pybot/plugins/airtable/plugin.py deleted file mode 100644 index c5307a70..00000000 --- a/pybot/plugins/airtable/plugin.py +++ /dev/null @@ -1,55 +0,0 @@ -import asyncio -import logging -import os -from collections import defaultdict - -from pybot.plugins.airtable import endpoints -from pybot.plugins.airtable.api import AirtableAPI - -logger = logging.getLogger(__name__) - - -class AirtablePlugin: - __name__ = "airtable" - - def __init__(self): - self.session = None # set lazily on plugin load - self.api_key = None - self.base_key = None - self.api = None - self.verify = None - - self.routers = {"request": RequestRouter()} - - def load(self, sirbot, api_key=None, base_key=None, verify=None): - self.session = sirbot.http_session - self.api_key = api_key or os.environ.get("AIRTABLE_API_KEY", "") - self.base_key = base_key or os.environ.get("AIRTABLE_BASE_KEY", "") - self.verify = verify or os.environ.get("AIRTABLE_VERIFY", "") - - self.api = AirtableAPI(self.session, self.api_key, self.base_key) - - sirbot.router.add_route("POST", "/airtable/request", endpoints.incoming_request) - - def on_request(self, request, handler, **kwargs): - if not asyncio.iscoroutinefunction(handler): - handler = asyncio.coroutine(handler) - options = {**kwargs, "wait": False} - self.routers["request"].register(request, (handler, options)) - - -class RequestRouter: - def __init__(self): - self._routes = defaultdict(list) - - def register(self, request_type, handler, **detail): - logger.info("Registering %s, %s to %s", request_type, detail, handler) - self._routes[request_type].append(handler) - - def dispatch(self, request): - logger.debug('Dispatching request "%s"', request.get("type")) - if request["type"] in self._routes: - for handler in self._routes.get(request["type"]): - yield handler - else: - return diff --git a/pybot/plugins/api/__init__.py b/pybot/plugins/api/__init__.py deleted file mode 100644 index 1c3af52e..00000000 --- a/pybot/plugins/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plugin import APIPlugin diff --git a/pybot/plugins/api/endpoints.py b/pybot/plugins/api/endpoints.py deleted file mode 100644 index f740ea88..00000000 --- a/pybot/plugins/api/endpoints.py +++ /dev/null @@ -1,52 +0,0 @@ -import asyncio -import json -import logging - -from aiohttp.web_response import Response - -from pybot.plugins.api.request import FailedVerification, SlackApiRequest - -logger = logging.getLogger(__name__) - - -async def slack_api(request): - api_plugin = request.app.plugins["api"] - - try: - slack_request = SlackApiRequest.from_request(request) - except FailedVerification: - logger.info(f"Failed verification to API route {request.url}.") - return Response(status=401) - - futures = list(_dispatch(api_plugin.routers["slack"], slack_request, request.app)) - - if futures: - return await _wait_and_check_result(futures) - return Response(status=200) - - -def _dispatch(router, event, app): - for handler, configuration in router.dispatch(event): - f = asyncio.ensure_future(handler(event, app)) - yield f - - -async def _wait_and_check_result(futures): - dones, _ = await asyncio.wait(futures, return_when=asyncio.ALL_COMPLETED) - try: - results = [done.result() for done in dones] - except Exception as e: - logger.exception(e) - return Response(status=500) - - if len(results) > 1: - logger.warning("Multiple web.Response for handler, returning none") - - elif results: - result = ( - results[0] - if isinstance(results[0], Response) - else Response(body=json.dumps(results[0])) - ) - - return result diff --git a/pybot/plugins/api/plugin.py b/pybot/plugins/api/plugin.py deleted file mode 100644 index 4e3061f5..00000000 --- a/pybot/plugins/api/plugin.py +++ /dev/null @@ -1,49 +0,0 @@ -import asyncio -import logging -from collections import defaultdict - -from pybot.plugins.api import endpoints - -logger = logging.getLogger(__name__) - - -class APIPlugin: - __name__ = "api" - - def __init__(self): - self.session = None - self.routers = {"slack": SlackAPIRequestRouter()} - - def load(self, sirbot): - self.session = sirbot.http_session - - sirbot.router.add_route( - "GET", "/pybot/api/v1/slack/{resource}", endpoints.slack_api - ) - sirbot.router.add_route( - "POST", "/pybot/api/v1/slack/{resource}", endpoints.slack_api - ) - - def on_get(self, request, handler, **kwargs): - if not asyncio.iscoroutinefunction(handler): - handler = asyncio.coroutine(handler) - options = {**kwargs, "wait": False} - self.routers["slack"].register(request, (handler, options)) - - -class SlackAPIRequestRouter: - def __init__(self): - self._routes = defaultdict(list) - - def register(self, resource, handler, **detail): - logger.info(f"Registering {resource}, {detail} to {handler}") - self._routes[resource].append(handler) - - def dispatch(self, request): - resource = request.resource - logger.debug(f"Dispatching request {resource}") - if resource in self._routes: - for handler in self._routes.get(resource): - yield handler - else: - return diff --git a/pybot/plugins/api/request.py b/pybot/plugins/api/request.py deleted file mode 100644 index fa4554d5..00000000 --- a/pybot/plugins/api/request.py +++ /dev/null @@ -1,88 +0,0 @@ -import copy -import os -from typing import MutableMapping - -BACKEND_AUTH_TOKEN = os.environ.get("BACKEND_AUTH_TOKEN", "devBackendToken") - - -class SlackApiRequest(MutableMapping): - """ - MutableMapping representing an api query request. Shamelessly stolen from pyslackers/slack-sansio - - Attributes: - resource: The resource the request was made for (i.e. the last part of the request url) - - query: Querystring params as a dict - - token: Bearer Token provided with request - """ - - auth_tokens = {BACKEND_AUTH_TOKEN} - - def __init__(self, raw_request, resource, query): - self.request = raw_request - self.resource = resource - self.query = query - self.token = self.__get_token(raw_request) - - if not self.authorized: - raise FailedVerification(self.token) - - @property - def authorized(self): - return self.token is not None and self.token in self.auth_tokens - - async def json(self): - if self.request.can_read_body: - return await self.request.json() - else: - return {} - - @classmethod - def from_request(cls, raw_request): - resource = raw_request.match_info["resource"] - query = raw_request.query - - return cls(raw_request, resource, query) - - @staticmethod - def __get_token(raw_request): - if "Authorization" in raw_request.headers: - auth_header = raw_request.headers["Authorization"] - if auth_header.startswith("Bearer "): - return auth_header[7:] - return None - - def __getitem__(self, item): - return self.request[item] - - def __setitem__(self, key, value): - self.request[key] = value - - def __delitem__(self, key): - del self.request[key] - - def __iter__(self): - return iter(self.request) - - def __len__(self): - return len(self.request) - - def __repr__(self): - return "API Request: " + str(self.request) - - def clone(self) -> "SlackApiRequest": - return self.__class__( - copy.deepcopy(self.request), - copy.deepcopy(self.resource), - copy.deepcopy(self.query), - ) - - -class FailedVerification(Exception): - """ - Raised when incoming API request fails verification - """ - - def __init__(self, token: str) -> None: - self.token = token diff --git a/pyproject.toml b/pyproject.toml index 0f3de3fb..f4b55c47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,143 @@ [tool.poetry] name = "operationcode-pybot" -version = "2.1" -description = "Operation Code's Official Slackbot" -authors = ["Allen Anthes "] -license = "MIT" +version = "0.9.0" +description = "Slack bot for Operation Code Slack." +authors = ["Judson Stevens "] [tool.poetry.dependencies] -python = "^3.7" -aiocontextvars = "^0.2.2" -cchardet = "^2.1.6" -cython = "^0.29.21" -python-dotenv = "^0.14.0" -pyyaml = "^5.3.1" -sentry-sdk = "^0.17.8" -sirbot = "^0.1.1" -zipcodes = "^1.1.2" - -[tool.poetry.dev-dependencies] -asynctest = "^0.13.0" -black = {version = "^20.8b1", allow-prereleases = true} -flake8 = "^3.8.3" -isort = "^4.3" -pytest = "^6.0.2" -pytest-aiohttp = "^0.3.0" -pytest-asyncio = "^0.14.0" -pytest-mock = "^3.3.1" -requests = "^2.22" +aiohttp = "~3" +APScheduler = "~3" +fastapi = "~0" +pydantic = "~2" +pyairtable = "~1" +pytesseract = "~0" +python = "~3.10" +requests = "~2" +sentence-transformers = "~2" +sentry-sdk = "~1" +slack-bolt = "~1" +tensorboard = "~2" +unstructured = "~0" +uvicorn = {extras = ["standard"], version = "~0"} +weaviate-client = "~3" +pdf2image = "^1.16.3" +pypdf = "^3.8.1" + + +[tool.poetry.group.dev.dependencies] +black = "~23" +mypy = "~1" +pyaml = "~21" +pylint = "~2" +pytest = "~7" +pytest-vcr = "~1" +ruff = "~0" [build-system] -requires = ["poetry>=0.12"] +requires = ["poetry>=1.3"] build-backend = "poetry.masonry.api" + +[tool.ruff] +# All rules to be enabled - https://beta.ruff.rs/docs/rules/ +select = ["E", "F", "W", "B", "C90", "I", "N", "D", "UP", "YTT", "ANN", "S", "BLE", "FBT", + "B", "A", "COM", "C4", "DTZ", "T10", "DJ", "EM", "EXE", "ISC", "ICN", "G", "INP", "PIE", + "T20", "PYI", "PT", "Q", "RSE", "RET", "SLF", "SIM", "TID", "TCH", "ARG", "PTH", "ERA", + "PD", "PGH", "PL", "TRY", "RUF"] +ignore = [ + "B008", # Ignore B008 because it complains about how FastAPI handles Depends + "D213", # We want the multi-line summary on the first line + "D203", # We want no blank line before class docstring +] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["A", "B", "C", "D", "E", "F", "I", "COM812"] +unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + # Exclude the one off script file + "one_offs.py", + # Exclude the vector search testing + "vector*", + # Exclude the databases folder + "databases/**", +] + +# Same as Black. +line-length = 119 + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Python 3.10. +target-version = "py310" + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.ruff.per-file-ignores] +"**/database/*" = [ + "A003", # We should allow shadowing + "ANN002", # We don't want to type *args + "ANN003", # We don't want to type **kwargs +] +"**/schemas/*" = [ + "A003", # We should allow shadowing + "D106", # We should allow missing docstring +] +"**/models/*" = [ + "A003", # We should allow shadowing + "D106", # We should allow missing docstring +] +"**/routers/*" = [ + "BLE001", # We use broad exceptions + "TRY300", # We don't want to use if/else in returns + "FBT001", # We are fine with Boolean positional args in function definition +] +"**/actions/*" = [ + "BLE001", # We use broad exceptions + "TRY300", # We don't want to use if/else in returns +] +"**/alembic/*" = [ + "ANN201", # We don't want return type annotation + "D103", # We don't care about docstrings + "INP001", # We don't want an __init__ in the root directory + "PLR0915", # We don't care about too many statements + "PTH120", # We want to use os.path for now + "PTH100", # we want to use os.path for now +] +"**/tests/*" = [ + "S101", # We use assert in tests + "ANN101", # We don't need to type self in tests +] + + +[tool.black] +line-length = 119 +exclude = [".idea", "docs/"] + +[tool.mypy] +exclude = [ + "/.idea/", + ".idea/*.py" +] \ No newline at end of file diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 00000000..85ac14fc --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.10.2 \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..e7160c89 --- /dev/null +++ b/setup.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +ngrok_pid=$(pgrep ngrok) + +check=$? + +# check if the exit status returned success +if [ $check -eq 0 ]; then + echo "Current ngrok PID = ${ngrok_pid}" + echo "Killing current Ngrok instance..." + kill -9 "$ngrok_pid" + check=$? + sleep 2 + if [ $check -eq 0 ]; then + echo "Successfully killed previous Ngrok, starting new instance..." + ngrok http 80 --log=stdout > ngrok.log & + echo "Waiting for 5 seconds so Ngrok can start..." + sleep 5 + + # shellcheck disable=SC2155 + export NGROK_URL=$(curl http://localhost:4040/api/tunnels --silent | python -c "import json, sys; print(json.load(sys.stdin)['tunnels'][1]['public_url'])") + echo "New Ngrok URL is: $NGROK_URL" + + echo "Please enter a name for your bot: " + read -r bot_name + export BOT_NAME=${bot_name} + + echo "Please enter in your first initial and last name - for example - 'jstevens'; this is what will be at the end of your slash commands in the Slack workspace: " + read -r bot_username + export BOT_USERNAME=${bot_username} + else + echo "Failed to kill previous Ngrok, ending execution..." + fi +elif [ $check -eq 1 ]; then + echo "No previous ngrok PID found, starting new instance..." + ngrok http 80 --log=stdout > ngrok.log & +else + echo "Problem locating and/or killing an existing Ngrok instance, ending execution..." +fi + + diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..d420712d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests.""" diff --git a/tests/conftest.py b/tests/conftest.py index 8d780154..f8a09145 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,55 +1,14 @@ -import copy - +"""Test configuration.""" import pytest -from sirbot import SirBot -from sirbot.plugins.slack import SlackPlugin - -from pybot import endpoints -from pybot.plugins import AirtablePlugin, APIPlugin -from tests import data - -pytest_plugins = ("slack.tests.plugin",) - - -@pytest.fixture(params={**data.Action.__members__}) -def action(request): - if isinstance(request.param, str): - payload = copy.deepcopy(data.Action[request.param].value) - else: - payload = copy.deepcopy(request.param) - return payload - - -@pytest.fixture -async def bot(loop) -> SirBot: - b = SirBot() - slack = SlackPlugin( - token="token", - verify="supersecuretoken", - bot_user_id="bot_user_id", - bot_id="bot_id", - ) - airtable = AirtablePlugin() - endpoints.slack.create_endpoints(slack) - - api = APIPlugin() - endpoints.api.create_endpoints(api) - - b.load_plugin(slack) - b.load_plugin(airtable) - b.load_plugin(api) - return b +@pytest.fixture(scope="module") +def vcr_config() -> dict[str, list[tuple[str, str]]]: + """VCR configuration for the tests. -@pytest.fixture -def slack_bot(bot: SirBot): - slack = SlackPlugin( - token="token", - verify="supersecuretoken", - bot_user_id="bot_user_id", - bot_id="bot_id", - ) - endpoints.slack.create_endpoints(slack) - bot.load_plugin(slack) - return bot + :return: A replacement for the Authorization header in cassettes. + """ + return { + # Replace the Authorization request header with "DUMMY" in cassettes + "filter_headers": [("Authorization", "DUMMY")], + } diff --git a/tests/data/__init__.py b/tests/data/__init__.py deleted file mode 100644 index 93743997..00000000 --- a/tests/data/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .actions import Action diff --git a/tests/data/actions.py b/tests/data/actions.py deleted file mode 100644 index e86948ed..00000000 --- a/tests/data/actions.py +++ /dev/null @@ -1,58 +0,0 @@ -import json -from enum import Enum - -claim_event = { - "type": "interactive_message", - "user": {"id": "U123"}, - "actions": [{"name": "rec123", "value": "mentee_claimed"}], - "original_message": { - "text": "some text", - "attachments": [ - { - "text": "some text", - "actions": [{"name": "rec123", "value": "mentee_unclaimed"}], - } - ], - }, - "channel": {"id": "abc"}, - "message_ts": "123123.123", - "callback_id": "claim_mentee", - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} - -unclaim_event = { - "type": "interactive_message", - "user": {"id": "U123"}, - "actions": [{"name": "rec123", "value": "mentee_unclaimed"}], - "original_message": { - "text": "some text", - "attachments": [ - { - "text": "some text", - "actions": [{"name": "rec123", "value": "mentee_unclaimed"}], - } - ], - }, - "channel": {"id": "abc"}, - "message_ts": "123123.123", - "callback_id": "claim_mentee", - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} - -raw_claim_event = {"payload": json.dumps(claim_event)} -raw_unclaim_event = {"payload": json.dumps(unclaim_event)} - - -class Action(Enum): - claim_mentee = raw_claim_event - unclaim_mentee = raw_unclaim_event diff --git a/tests/data/events.py b/tests/data/events.py deleted file mode 100644 index ae26631c..00000000 --- a/tests/data/events.py +++ /dev/null @@ -1,166 +0,0 @@ -TEAM_JOIN = { - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "event": { - "type": "team_join", - "channel": "C00000A00", - "user": { - "id": "U0AAAA", - "team_id": "T000AAA0A", - "name": "test", - "real_name": "test testerson", - }, - "event_ts": "123456789.000001", - }, - "type": "event_callback", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} - -new_message = { - "type": "message", - "user": "U8FDR1603", - "text": "test3", - "client_msg_id": "025cc728-fcb5-4dd7-8920-619a605bb631", - "ts": "1540497949.000100", - "channel": "GDNHHNCTV", - "event_ts": "1540497949.000100", - "channel_type": "mpim", -} -edit_message = { - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "type": "event_callback", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, - "event": { - "type": "message", - "message": { - "type": "message", - "user": "U8FDR1603", - "text": "nevermind", - "client_msg_id": "9aff714d-8674-42fc-986c-0c1c06cca3fc", - "edited": {"user": "U8FDR1603", "ts": "1540497210.000000"}, - "ts": "1540497204.000100", - }, - "subtype": "message_changed", - "hidden": True, - "channel": "C8DA69KM4", - "previous_message": { - "type": "message", - "user": "U8FDR1603", - "text": "two", - "client_msg_id": "9aff714d-8674-42fc-986c-0c1c06cca3fc", - "ts": "1540497204.000100", - }, - "event_ts": "1540497210.000100", - "ts": "1540497210.000100", - "channel_type": "channel", - }, -} -delete_message = { - "type": "message", - "deleted_ts": "1540497676.000100", - "subtype": "message_deleted", - "hidden": True, - "channel": "GDNHHNCTV", - "previous_message": { - "type": "message", - "user": "U8FDR1603", - "text": "testing3", - "client_msg_id": "b5694bd6-6ed0-4ddd-bf84-3e2c8165c624", - "ts": "1540497676.000100", - }, - "event_ts": "1540497684.000100", - "ts": "1540497684.000100", - "channel_type": "mpim", -} - -MESSAGE_EDIT = { - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "event": { - "type": "message", - "message": { - "type": "message", - "user": "U000AA000", - "text": "hello world", - "edited": {"user": "U000AA000", "ts": "1513882449.000000"}, - "ts": "123456789.000001", - }, - "subtype": "message_changed", - "hidden": True, - "channel": "C00000A00", - "previous_message": { - "type": "message", - "user": "U000AA000", - "text": "foo bar", - "ts": "123456789.000001", - }, - "event_ts": "123456789.000002", - "ts": "123456789.000002", - }, - "type": "event_callback", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} - -MESSAGE_DELETE = { - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "event": { - "type": "message", - "message": { - "type": "message", - "user": "U000AA000", - "text": "hello world", - "edited": {"user": "U000AA000", "ts": "1513882449.000000"}, - "ts": "123456789.000001", - }, - "subtype": "message_deleted", - "hidden": True, - "channel": "C00000A00", - "previous_message": { - "type": "message", - "user": "U000AA000", - "text": "foo bar", - "ts": "123456789.000001", - }, - "event_ts": "123456789.000002", - "ts": "123456789.000002", - }, - "type": "event_callback", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} - -PLAIN_MESSAGE = { - "token": "supersecuretoken", - "team_id": "T000AAA0A", - "api_app_id": "A0AAAAAAA", - "event": { - "type": "message", - "message": { - "type": "message", - "user": "U000AA000", - "text": "hello world", - "edited": {"user": "U000AA000", "ts": "1513882449.000000"}, - "ts": "123456789.000001", - }, - "channel": "C00000A00", - "event_ts": "123456789.000002", - "ts": "123456789.000002", - }, - "type": "event_callback", - "authed_teams": ["T000AAA0A"], - "event_id": "AAAAAAA", - "event_time": 123456789, -} diff --git a/tests/endpoints/__init__.py b/tests/endpoints/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/endpoints/api/__init__.py b/tests/endpoints/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/endpoints/api/test_slack_api_endpoint.py b/tests/endpoints/api/test_slack_api_endpoint.py deleted file mode 100644 index 6191be72..00000000 --- a/tests/endpoints/api/test_slack_api_endpoint.py +++ /dev/null @@ -1,47 +0,0 @@ -import json - -import pytest -from asynctest import CoroutineMock -from sirbot import SirBot - -MOCK_USER_NAME = "userName" -MOCK_USER_ID = "U8N6XBL7Q" -AUTH_HEADER = {"Authorization": "Bearer devBackendToken"} - -VALID_SLACK_RESPONSE = CoroutineMock( - return_value={"user": {"exists": True, "id": MOCK_USER_ID, "name": MOCK_USER_NAME}} -) - - -@pytest.mark.parametrize( - "headers, status", - [ - ({"Authorization": "Bearer devBackendToken"}, 200), - ({"Authorization": "Bearer abc"}, 401), - (None, 401), - ], -) -async def test_detect_credentials(bot: SirBot, aiohttp_client, headers, status): - bot.plugins["slack"].api.query = VALID_SLACK_RESPONSE - client = await aiohttp_client(bot) - - res = await client.get( - "/pybot/api/v1/slack/verify?email=test@test.test", headers=headers - ) - - assert res.status == status - - -async def test_verify_returns_correct_success_params(bot: SirBot, aiohttp_client): - client = await aiohttp_client(bot) - - bot.plugins["slack"].api.query = VALID_SLACK_RESPONSE - - res = await client.get( - "/pybot/api/v1/slack/verify?email=test@test.test", headers=AUTH_HEADER - ) - body = json.loads(await res.text()) - - assert body["exists"] is True - assert body["id"] == MOCK_USER_ID - assert body["displayName"] == MOCK_USER_NAME diff --git a/tests/endpoints/slack/__init__.py b/tests/endpoints/slack/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/endpoints/slack/test_slack_actions.py b/tests/endpoints/slack/test_slack_actions.py deleted file mode 100644 index 62806b56..00000000 --- a/tests/endpoints/slack/test_slack_actions.py +++ /dev/null @@ -1,34 +0,0 @@ -from asynctest import CoroutineMock -from sirbot import SirBot - - -async def test_claim_mentee_response_attachment_is_list( - action: dict, aiohttp_client, bot: SirBot -): - client, slack_mock = await create_mocks(aiohttp_client, bot) - - await client.post("/slack/actions", data=action) - assert isinstance(slack_mock.call_args[0][1]["attachments"], list) - - -async def test_claim_mentee_response_contains_original_text( - action: dict, aiohttp_client, bot: SirBot -): - client, slack_mock = await create_mocks(aiohttp_client, bot) - await client.post("/slack/actions", data=action) - request_payload = slack_mock.call_args[0][1] - assert request_payload["text"] is not None - - -async def create_mocks(aiohttp_client, bot): - slack_mock = CoroutineMock( - return_value={"user": {"profile": {"email": "email@email.com"}}} - ) - airtable_mock = CoroutineMock(return_value="U123") - bot["plugins"]["slack"].api.query = slack_mock - bot["plugins"]["airtable"].api.find_records = CoroutineMock(return_value=[]) - bot["plugins"]["airtable"].api.update_request = airtable_mock - bot["plugins"]["airtable"].api.get_name_from_record_id = airtable_mock - bot["plugins"]["airtable"].api.get_row_from_record_id = airtable_mock - client = await aiohttp_client(bot) - return client, slack_mock diff --git a/tests/endpoints/slack/test_slack_events.py b/tests/endpoints/slack/test_slack_events.py deleted file mode 100644 index 73078a0f..00000000 --- a/tests/endpoints/slack/test_slack_events.py +++ /dev/null @@ -1,41 +0,0 @@ -import asyncio -import logging - -import asynctest -from asynctest import CoroutineMock - -from pybot import endpoints -from pybot.endpoints.slack.events import create_endpoints, team_join -from tests.data.events import MESSAGE_DELETE, MESSAGE_EDIT, PLAIN_MESSAGE, TEAM_JOIN - - -async def test_team_join_handler_exists(bot): - endpoints.slack.create_endpoints(bot["plugins"]["slack"]) - - assert asynctest.asyncio.iscoroutinefunction( - bot["plugins"]["slack"].routers["event"]._routes["team_join"]["*"]["*"][0][0] - ) - - -async def test_edits_are_logged(bot, aiohttp_client, caplog): - client = await aiohttp_client(bot) - - with caplog.at_level(logging.INFO): - await client.post("/slack/events", json=MESSAGE_EDIT) - assert any("CHANGE_LOGGING: edited" in record.message for record in caplog.records) - - -async def test_deletes_are_logged(bot, aiohttp_client, caplog): - client = await aiohttp_client(bot) - - with caplog.at_level(logging.INFO): - await client.post("/slack/events", json=MESSAGE_DELETE) - assert any("CHANGE_LOGGING: deleted" in record.message for record in caplog.records) - - -async def test_no_other_messages_logged(bot, aiohttp_client, caplog): - client = await aiohttp_client(bot) - - with caplog.at_level(logging.INFO): - await client.post("/slack/events", json=PLAIN_MESSAGE) - assert not any("CHANGE_LOGGING" in record.message for record in caplog.records) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e0310a01 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +"""Unit tests.""" diff --git a/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..e4fecab6 --- /dev/null +++ b/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml @@ -0,0 +1,61 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Daily%20Programmer?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA42RXUvDMBiF/0rJdTvSOLuZK92csKmb0KI4kRGaWOKyZDTp7Bj973vb+lHBgZCL + 8J4nJycnB5SL1OTcIvpyQJIjWg+SyXZmlviCbBfRc4l89CaFqpkDSkTpAHLCOqmzoBWC3HwANWZO + ZCbfn9LnbCNOaXfMOu/BWCe4t9BAEUxIgENYSUjo2ZBi3MMYL4GdaukkU2r/3wOf2NgUGsJjH8Wq + yH6SrNokq06Se8MlTHnXlyThOe1HFIdfvr9Qb7SvC2o6LGx+VUzK28H15OlmLqcRwGLDpALtveDW + 6B4k2gltL7N63EvNBhDdFjRrCC9uCVT56JGpxlfqXbP77sCLFUvXXhKf6nWkTLq2f6tgnOYCPo0n + srm4W+GQkoj2B+1Tq9fqCPRzvKsqAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '309' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sun, 02 Jan 2022 15:47:21 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwgLCpmfGloKV5qC; path=/; expires=Mon, 02 Jan 2023 15:47:21 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '554' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..0aeb9ece --- /dev/null +++ b/tests/unit/cassettes/TestDailyProgrammerTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml @@ -0,0 +1,63 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwgLCpmfGloKV5qC + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Daily%20Programmer?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA42RXUvDMBiF/0rJdTvSOLuZK92csKmb0KI4kRGaWOKyZDTp7Bj973vb+lHBgZCL + 8J4nJycnB5SL1OTcIvpyQJIjWg+SyXZmlviCbBfRc4l89CaFqpkDSkTpAHLCOqmzoBWC3HwANWZO + ZCbfn9LnbCNOaXfMOu/BWCe4t9BAEUxIgENYSUjo2ZBi3MMYL4GdaukkU2r/3wOf2NgUGsJjH8Wq + yH6SrNokq06Se8MlTHnXlyThOe1HFIdfvr9Qb7SvC2o6LGx+VUzK28H15OlmLqcRwGLDpALtveDW + 6B4k2gltL7N63EvNBhDdFjRrCC9uCVT56JGpxlfqXbP77sCLFUvXXhKf6nWkTLq2f6tgnOYCPo0n + srm4W+GQkoj2B+1Tq9fqCPRzvKsqAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '309' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sun, 02 Jan 2022 15:47:21 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwgLCpmfGloKV5qC; path=/; expires=Mon, 02 Jan 2023 15:47:21 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '554' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..42351ac2 --- /dev/null +++ b/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_all_desired_fields.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Mentors?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA32RSW/CMBCF/4rlc0Bx1JYqJ3ZogXSBgkrVg5sM1I3JIC90Qfz32iGn0lbywXrz + zczTmz1VkKLKNI2f9lRkNPZCg2+TvHXJxufD5eqGBnQlQHpmT+/xnVx1aczCs4DOuSxbRLErfwGd + Sp7mJOEbcLIBbUSxrh27awrfHTHm2pAJZsKpvjcKo6gWMvdmrBFHLGasHobh8idK2p9+f7nPatWy + vY9Ro9tb9BNxdeFg2HAhXe3NZhqLujawg0I3116up7hxSHG0dV0SZHok6CGgfSvlv6Z71fDTYtNL + 1fxWasTOz+hzqcGnkQspfbQ+1Pxuzj7PZ+ar04PxA30O6IR/kAkUBsAxLKBtgX8ZSNB46PfiTGyA + LLH4034XtFAuQr8MlX4VWzJEqzS5BUUWAHm5vrJCFqhyBy+Eea2s22E0fxk+DqajVWKlt97BDAiu + SAeLzKaGtNIUtsYf1CgLAR1YkcELYk7ugVeqCzpVwB3lDZ+ePozDxvH0h+fDN8ty/B+ZAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '396' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:31 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwOluKvpW4Hx5E7F; path=/; expires=Sun, 01 Jan 2023 17:38:31 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '665' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..ead3ee9c --- /dev/null +++ b/tests/unit/cassettes/TestMentorTableBasic.test_mentor_table_has_correct_number_of_fields.yaml @@ -0,0 +1,64 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwOluKvpW4Hx5E7F + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Mentors?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA32RSW/CMBCF/4rlc0Bx1JYqJ3ZogXSBgkrVg5sM1I3JIC90Qfz32iGn0lbywXrz + zczTmz1VkKLKNI2f9lRkNPZCg2+TvHXJxufD5eqGBnQlQHpmT+/xnVx1aczCs4DOuSxbRLErfwGd + Sp7mJOEbcLIBbUSxrh27awrfHTHm2pAJZsKpvjcKo6gWMvdmrBFHLGasHobh8idK2p9+f7nPatWy + vY9Ro9tb9BNxdeFg2HAhXe3NZhqLujawg0I3116up7hxSHG0dV0SZHok6CGgfSvlv6Z71fDTYtNL + 1fxWasTOz+hzqcGnkQspfbQ+1Pxuzj7PZ+ar04PxA30O6IR/kAkUBsAxLKBtgX8ZSNB46PfiTGyA + LLH4034XtFAuQr8MlX4VWzJEqzS5BUUWAHm5vrJCFqhyBy+Eea2s22E0fxk+DqajVWKlt97BDAiu + SAeLzKaGtNIUtsYf1CgLAR1YkcELYk7ugVeqCzpVwB3lDZ+ePozDxvH0h+fDN8ty/B+ZAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '396' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:32 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwOluKvpW4Hx5E7F; path=/; expires=Sun, 01 Jan 2023 17:38:32 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '665' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..ca334fb9 --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Affiliations?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA3WQT0+DQBDFvwrZM5AFbWs4aW21tpZDISXaNA3CFNfALu4f1BC+u7vswaSJyR4m + 7/125s30iEPBeClQdOgRKVFkhA7SXcvu122G37MQuehMoDZMj5JaVRqSICSh1ckaJ86+NLXP67ED + od1YuSjOG/ijPUt7ln7OhXS2rCRaNb9CHIYeDvRLg1kUTKKrGx9j/HqJOvMfE2ScpAS/U8vvzWyx + zB5i8jTVMDQ5qbX3oUrBqC8kdEDFbWVkv2CNRqiNtR4JJ7EEGly0AFFw0krC6H+xt0Al484OPpW2 + zd3MxdQq3L+tXh6TzTlWNTrqXgWHXEKZknHWxXbX0WRqtxuOwy8BmLuRhQEAAA== + headers: + Connection: + - keep-alive + Content-Length: + - '274' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:46:25 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwrvNzjVswDW2j23; path=/; expires=Sun, 01 Jan 2023 17:46:25 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '389' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..d1c2ae6a --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipAffiliationTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwrvNzjVswDW2j23 + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Affiliations?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA3WQT0+DQBDFvwrZM5AFbWs4aW21tpZDISXaNA3CFNfALu4f1BC+u7vswaSJyR4m + 7/125s30iEPBeClQdOgRKVFkhA7SXcvu122G37MQuehMoDZMj5JaVRqSICSh1ckaJ86+NLXP67ED + od1YuSjOG/ijPUt7ln7OhXS2rCRaNb9CHIYeDvRLg1kUTKKrGx9j/HqJOvMfE2ScpAS/U8vvzWyx + zB5i8jTVMDQ5qbX3oUrBqC8kdEDFbWVkv2CNRqiNtR4JJ7EEGly0AFFw0krC6H+xt0Al484OPpW2 + zd3MxdQq3L+tXh6TzTlWNTrqXgWHXEKZknHWxXbX0WRqtxuOwy8BmLuRhQEAAA== + headers: + Connection: + - keep-alive + Content-Length: + - '274' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:46:26 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwrvNzjVswDW2j23; path=/; expires=Sun, 01 Jan 2023 17:46:26 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '389' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..a9d60be4 --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml @@ -0,0 +1,63 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Mentor%20Requests?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA32S3VLbMBCFX0Wj6yQjuw1JfUV+TEwSwtQ2ZKDDhWqvjbAipZYcGjK8O5IVOsBA + Z3zj3W/Psc/uAdeQyTpXOPh1wCzHgS00kX/9O7qZJYti1XDcwQUDbpkDjkGBRhPZCI0D0sGxfETn + Zsz77nfwkiqNLmTODG+lfOL7XeKZJ/UGge8FZNAjhNziDyga7612a9+oetSEfxeDabg+W7HzEwPD + hjJueg9NrqToKQ07EOq0tOVeJjcGEXQDhpi3BEocgZ87OOE0q9DKtTUozUTZdf/TreWjGQ2P6m+a + rndqK0f9BOody6xGesTOnEbcaoyKgnFGNZPCJGkzXDD/ptrKp3saj8PSIKa2gzTeysl8uyb3ax/f + mbk8Z3aIcjQFbb5DvXF495VJxTg32SsUw5/GEDbh1qn6ee3t+6l+moSwvHJO6qGcRftyGmX94Rys + k8vhApSiJaA0+cpnwinbWG1dN/Dvtd1Q68bLKhnuvDGZiXoknNuAblfVaOgt+9FtcWndXscuxYcz + MDfwIyDk9QzcOVnxz3fj+v9XMUvOaqAmkJS1W35/dmQYfDs5knfPL7PR6AbxAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '447' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:46:26 GMT + Server: + - Tengine + Set-Cookie: + - brw=brw6yqQpAzIJ7BgE8; path=/; expires=Sun, 01 Jan 2023 17:46:26 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '753' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..b3aad076 --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipRequestsTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brw6yqQpAzIJ7BgE8 + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Mentor%20Requests?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA32S3VLbMBCFX0Wj6yQjuw1JfUV+TEwSwtQ2ZKDDhWqvjbAipZYcGjK8O5IVOsBA + Z3zj3W/Psc/uAdeQyTpXOPh1wCzHgS00kX/9O7qZJYti1XDcwQUDbpkDjkGBRhPZCI0D0sGxfETn + Zsz77nfwkiqNLmTODG+lfOL7XeKZJ/UGge8FZNAjhNziDyga7612a9+oetSEfxeDabg+W7HzEwPD + hjJueg9NrqToKQ07EOq0tOVeJjcGEXQDhpi3BEocgZ87OOE0q9DKtTUozUTZdf/TreWjGQ2P6m+a + rndqK0f9BOody6xGesTOnEbcaoyKgnFGNZPCJGkzXDD/ptrKp3saj8PSIKa2gzTeysl8uyb3ax/f + mbk8Z3aIcjQFbb5DvXF495VJxTg32SsUw5/GEDbh1qn6ee3t+6l+moSwvHJO6qGcRftyGmX94Rys + k8vhApSiJaA0+cpnwinbWG1dN/Dvtd1Q68bLKhnuvDGZiXoknNuAblfVaOgt+9FtcWndXscuxYcz + MDfwIyDk9QzcOVnxz3fj+v9XMUvOaqAmkJS1W35/dmQYfDs5knfPL7PR6AbxAgAA + headers: + Connection: + - keep-alive + Content-Length: + - '447' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:46:27 GMT + Server: + - Tengine + Set-Cookie: + - brw=brw6yqQpAzIJ7BgE8; path=/; expires=Sun, 01 Jan 2023 17:46:27 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '753' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..e625dd4d --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_all_desired_fields.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Services?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA23OW0vDMBQH8K9S8tyWtDI386RjE+ZliJuKiozYHsuRNJFc5qX0u5vLgzCEPIT/ + +eXkPxANjdKtIex5INgSFoKbozvz6n5uH+eiXlGSkzcEEcxANsJ1HlkwFmW3S4OdVp9e3XMRN6Dc + x1tO1ryHP10kXSR9xY3NrlWLPg2valrXBa382VZTVp0wOikppU+HNJt/hyLxJ2f0mVt+XU4Xy4fz + Na6OPYaeo/Czd9caJUtjYQ/SnHYhLhvVeyJTrYsosk0SZMzJAkyj8cOikv/X9qbRwC20W4w7DlrP + 2GSWWo8v4y88kykbXQEAAA== + headers: + Connection: + - keep-alive + Content-Length: + - '244' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:14 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwbGAe1ZDvQhU9Aj; path=/; expires=Sun, 01 Jan 2023 17:38:14 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '349' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..f710e0da --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipServicesTableBasic.test_mentorship_services_table_has_correct_number_of_fields.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwbGAe1ZDvQhU9Aj + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Services?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA23OW0vDMBQH8K9S8tyWtDI386RjE+ZliJuKiozYHsuRNJFc5qX0u5vLgzCEPIT/ + +eXkPxANjdKtIex5INgSFoKbozvz6n5uH+eiXlGSkzcEEcxANsJ1HlkwFmW3S4OdVp9e3XMRN6Dc + x1tO1ryHP10kXSR9xY3NrlWLPg2valrXBa382VZTVp0wOikppU+HNJt/hyLxJ2f0mVt+XU4Xy4fz + Na6OPYaeo/Czd9caJUtjYQ/SnHYhLhvVeyJTrYsosk0SZMzJAkyj8cOikv/X9qbRwC20W4w7DlrP + 2GSWWo8v4y88kykbXQEAAA== + headers: + Connection: + - keep-alive + Content-Length: + - '244' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:17 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwbGAe1ZDvQhU9Aj; path=/; expires=Sun, 01 Jan 2023 17:38:17 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '349' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..8f92631d --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_all_desired_fields.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Skillsets?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA2WQX2uDMBTFv4rkWSU61haf1tJO164+zNKxDSlObyVdTFj+uBXxuzfRlcEG9yGc + 88u999wOCSi5qCSK3jpEKhRZQZ7qODnXy6S8na0BuehIgFqmQxnVtYEUSEVYfRiNg+BfhtoXdOhA + WDu8XJQWDfzS3kh7I/1YSOVseUWMan+FOAw9HJjaBdPI1M3Exxi//kWdxdkuMkzSUsz16nszXa6e + 71PyMDEwNAWhxjvpSnLmSwUtMHlXW9kveWMQNq61HggnGwnUu2gLTHFhj2HPQOuPbNYGCxwzMWco + v/rOE3xqE+nK6STcvycvcbY5ppqi3DQqBRQKqh0ZBv2LFvxE6/P+AiCKxpmCAQAA + headers: + Connection: + - keep-alive + Content-Length: + - '276' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:32 GMT + Server: + - Tengine + Set-Cookie: + - brw=brweWqz6bh1Pd9Pan; path=/; expires=Sun, 01 Jan 2023 17:38:32 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '386' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..fb4d1d96 --- /dev/null +++ b/tests/unit/cassettes/TestMentorshipSkillsetsTableBasic.test_mentorship_skillsets_table_has_correct_number_of_fields.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brweWqz6bh1Pd9Pan + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Skillsets?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA2WQX2uDMBTFv4rkWSU61haf1tJO164+zNKxDSlObyVdTFj+uBXxuzfRlcEG9yGc + 88u999wOCSi5qCSK3jpEKhRZQZ7qODnXy6S8na0BuehIgFqmQxnVtYEUSEVYfRiNg+BfhtoXdOhA + WDu8XJQWDfzS3kh7I/1YSOVseUWMan+FOAw9HJjaBdPI1M3Exxi//kWdxdkuMkzSUsz16nszXa6e + 71PyMDEwNAWhxjvpSnLmSwUtMHlXW9kveWMQNq61HggnGwnUu2gLTHFhj2HPQOuPbNYGCxwzMWco + v/rOE3xqE+nK6STcvycvcbY5ppqi3DQqBRQKqh0ZBv2LFvxE6/P+AiCKxpmCAQAA + headers: + Connection: + - keep-alive + Content-Length: + - '276' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 17:38:33 GMT + Server: + - Tengine + Set-Cookie: + - brw=brweWqz6bh1Pd9Pan; path=/; expires=Sun, 01 Jan 2023 17:38:33 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '386' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..8284123b --- /dev/null +++ b/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Message%20Text?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA3WQXWvCMBSG/0rJtS1piwq52lcHOnUXlg4cQ0JzVrK1CSSpVUv/+/JxMRCEXBze + 5znhTUakoJaKaUQ+R8QZIi4os+46XKt1kQ+/1TuaoW8OrXNGVMLZWMmANlw0cQCxkoO1nqmBRqrL + Pb6jHdxj+7Zv/tkxsGNgG6pNtJWM29Q1zHCWxTi1p0yXJE8JXiQY48OtGj1dXGX/ql6rx744vy1f + io/XHV8trAwd5a1lPz3TUiTawAmEfmhcnNSys4oIldfeiPbBQNMMVbT193Jx8pONagX2A1jJ/cpN + SUzmeSg5fU1/3z9ZSnYBAAA= + headers: + Connection: + - keep-alive + Content-Length: + - '245' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 18:14:51 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwmk4IX4aqVeCxyj; path=/; expires=Sun, 01 Jan 2023 18:14:50 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '374' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..e9f03ccc --- /dev/null +++ b/tests/unit/cassettes/TestMessageTextTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwmk4IX4aqVeCxyj + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Message%20Text?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA3WQXWvCMBSG/0rJtS1piwq52lcHOnUXlg4cQ0JzVrK1CSSpVUv/+/JxMRCEXBze + 5znhTUakoJaKaUQ+R8QZIi4os+46XKt1kQ+/1TuaoW8OrXNGVMLZWMmANlw0cQCxkoO1nqmBRqrL + Pb6jHdxj+7Zv/tkxsGNgG6pNtJWM29Q1zHCWxTi1p0yXJE8JXiQY48OtGj1dXGX/ql6rx744vy1f + io/XHV8trAwd5a1lPz3TUiTawAmEfmhcnNSys4oIldfeiPbBQNMMVbT193Jx8pONagX2A1jJ/cpN + SUzmeSg5fU1/3z9ZSnYBAAA= + headers: + Connection: + - keep-alive + Content-Length: + - '245' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 18:14:51 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwmk4IX4aqVeCxyj; path=/; expires=Sun, 01 Jan 2023 18:14:51 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '374' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml b/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml new file mode 100644 index 00000000..b6c820a3 --- /dev/null +++ b/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_all_desired_fields.yaml @@ -0,0 +1,61 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Scheduled%20Messages?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA22QXU/CMBSG/8rSa7Z0TfhwV6KDBBVuIJJoDKnrYauunbQdshD+u6ebiR+Q9KI5 + 5zntc94jMZBVRliSPB+JFCTxBTBiHG/Vx65IneqTHtlKKD1zJLcF1xpK5LJKqVpL14RbAPHKs3cE + H7h1wRK0Q4BRxkIa41nFccKuEkojSukTYguuAAkH1kmdh93zoak+sTcHa3kOwQoO7pv5C0wN7GrQ + WYPdlMuywdoMRSQvg5Q7CFdS4XjlPcS5x+iXR6s7r4TED/6jQ6/MBhfR4KbxYbR51daM68nhfphO + 1tOFnA0QBoVe2Hurha10ZB3sQdvr3JcjDA4R3UVw1xLBsiPIqUeWZZ3/ZLPpVt90q68L0Jc2Y2cJ + P/KytZN6397w4cwApiN8OuerjhLW72ZPL6cvnONUTBYCAAA= + headers: + Connection: + - keep-alive + Content-Length: + - '320' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 18:15:23 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwZfvUPfXAu7noUZ; path=/; expires=Sun, 01 Jan 2023 18:15:23 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '534' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml b/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml new file mode 100644 index 00000000..66c5f1c6 --- /dev/null +++ b/tests/unit/cassettes/TestScheduledMessagesTableBasic.test_mentorship_affiliation_table_has_correct_number_of_fields.yaml @@ -0,0 +1,63 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DUMMY + Connection: + - keep-alive + Cookie: + - brw=brwZfvUPfXAu7noUZ + User-Agent: + - python-requests/2.26.0 + method: GET + uri: https://api.airtable.com/v0/appHcVtFOf2HaGQog/Scheduled%20Messages?pageSize=1&maxRecords=1&view=Fields + response: + body: + string: !!binary | + H4sIAAAAAAAAA22QXU/CMBSG/8rSa7Z0TfhwV6KDBBVuIJJoDKnrYauunbQdshD+u6ebiR+Q9KI5 + 5zntc94jMZBVRliSPB+JFCTxBTBiHG/Vx65IneqTHtlKKD1zJLcF1xpK5LJKqVpL14RbAPHKs3cE + H7h1wRK0Q4BRxkIa41nFccKuEkojSukTYguuAAkH1kmdh93zoak+sTcHa3kOwQoO7pv5C0wN7GrQ + WYPdlMuywdoMRSQvg5Q7CFdS4XjlPcS5x+iXR6s7r4TED/6jQ6/MBhfR4KbxYbR51daM68nhfphO + 1tOFnA0QBoVe2Hurha10ZB3sQdvr3JcjDA4R3UVw1xLBsiPIqUeWZZ3/ZLPpVt90q68L0Jc2Y2cJ + P/KytZN6397w4cwApiN8OuerjhLW72ZPL6cvnONUTBYCAAA= + headers: + Connection: + - keep-alive + Content-Length: + - '320' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 01 Jan 2022 18:15:23 GMT + Server: + - Tengine + Set-Cookie: + - brw=brwZfvUPfXAu7noUZ; path=/; expires=Sun, 01 Jan 2023 18:15:23 GMT; domain=.airtable.com; + samesite=none; secure + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + access-control-allow-headers: + - authorization,content-length,content-type,user-agent,x-airtable-application-id,x-airtable-user-agent,x-api-version,x-requested-with + access-control-allow-methods: + - DELETE,GET,OPTIONS,PATCH,POST,PUT + access-control-allow-origin: + - '*' + airtable-uncompressed-content-length: + - '534' + content-encoding: + - gzip + status: + code: 200 + message: OK +version: 1 diff --git a/tests/unit/test_airtable.py b/tests/unit/test_airtable.py new file mode 100644 index 00000000..9d13b09d --- /dev/null +++ b/tests/unit/test_airtable.py @@ -0,0 +1,256 @@ +"""Tests for the Airtable module.""" +import pytest + +from modules.airtable import ( + daily_programmer_table, + mentor_table, + mentorship_affiliations_table, + mentorship_requests_table, + mentorship_services_table, + mentorship_skillsets_table, + message_text_table, + scheduled_message_table, +) + + +@pytest.mark.vcr() +class TestMentorTableBasic: + """Tests for the mentor table in Airtable.""" + + def setup(self) -> None: + """Set up for the tests.""" + self.desired_fields = { + "row_id", + "valid", + "slack_name", + "last_modified", + "last_modified_by", + "full_name", + "email", + "active", + "skills", + "max_mentees", + "bio", + "notes", + "time_zone", + "desired_mentorship_hours_per_week", + "mentees_worked_with", + "code_of_conduct_accepted", + "guidebook_read", + } + self.airtable_fields = mentor_table.table_fields + + def test_mentor_table_has_all_desired_fields(self) -> None: + """Test that the mentor table has all the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_mentor_table_has_correct_number_of_fields(self) -> None: + """Test that the mentor table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestMentorshipServicesTableBasic: + """Tests for the mentorship services table in Airtable.""" + + def setup(self) -> None: + """Set up for the tests.""" + self.desired_fields = { + "name", + "slug", + "description", + "last_modified", + "last_modified_by", + "valid", + } + self.airtable_fields = mentorship_services_table.table_fields + + def test_mentorship_services_table_has_all_desired_fields(self) -> None: + """Test that the mentorship services table has all the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_mentorship_services_table_has_correct_number_of_fields(self) -> None: + """Test that the mentorship services table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestMentorshipSkillsetsTableBasic: + """Tests for the mentorship skillsets table in Airtable.""" + + def setup(self) -> None: + """Set up for the tests.""" + self.desired_fields = { + "name", + "slug", + "mentors", + "mentor_requests", + "last_modified", + "last_modified_by", + "valid", + } + self.airtable_fields = mentorship_skillsets_table.table_fields + + def test_mentorship_skillsets_table_has_all_desired_fields(self) -> None: + """Test that the mentorship skillsets table has all the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_mentorship_skillsets_table_has_correct_number_of_fields(self) -> None: + """Test that the mentorship skillsets table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestMentorshipAffiliationTableBasic: + """Tests for the mentorship affiliation table in Airtable.""" + + def setup(self) -> None: + """Set up for the tests.""" + self.desired_fields = { + "name", + "slug", + "description", + "last_modified", + "last_modified_by", + "valid", + "mentor_requests", + } + self.airtable_fields = mentorship_affiliations_table.table_fields + + def test_mentorship_affiliation_table_has_all_desired_fields(self) -> None: + """Test that the mentorship affiliation table has all the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_mentorship_affiliation_table_has_correct_number_of_fields(self) -> None: + """Test that the mentorship affiliation table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestMentorshipRequestsTableBasic: + """Tests for the mentorship requests table in Airtable.""" + + def setup(self) -> None: + """Set up for the tests.""" + self.desired_fields = { + "slack_name", + "email", + "service", + "affiliation", + "additional_details", + "skillsets_requested", + "slack_message_ts", + "claimed", + "claimed_by", + "claimed_on", + "reset_by", + "reset_on", + "reset_count", + "last_modified", + "last_modified_by", + "row_id", + } + self.airtable_fields = mentorship_requests_table.table_fields + + def test_mentorship_affiliation_table_has_all_desired_fields(self) -> None: + """Test that the mentorship affiliation table has all the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_mentorship_affiliation_table_has_correct_number_of_fields(self) -> None: + """Test that the mentorship affiliation table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestScheduledMessagesTableBasic: + """Test the scheduled messages table.""" + + def setup(self) -> None: + """Set up the test.""" + self.desired_fields = { + "name", + "slug", + "channel", + "message_text", + "initial_date_time_to_send", + "frequency", + "last_sent", + "when_to_send", + "last_modified", + "last_modified_by", + "valid", + } + self.airtable_fields = scheduled_message_table.table_fields + + def test_scheduled_message_table_has_all_desired_fields(self) -> None: + """Ensure that the scheduled message table has the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_scheduled_message_table_has_correct_number_of_fields(self) -> None: + """Ensure that the scheduled message table has the correct number of fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestMessageTextTableBasic: + """Test the message text table.""" + + def setup(self) -> None: + """Set up the test.""" + self.desired_fields = { + "name", + "slug", + "text", + "category", + "last_modified", + "last_modified_by", + "valid", + } + self.airtable_fields = message_text_table.table_fields + + def test_message_text_table_has_all_desired_fields(self) -> None: + """Ensure that the message text table has the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_message_test_table_has_correct_number_of_fields(self) -> None: + """Ensure that the message text table has the desired fields.""" + assert len(self.airtable_fields) == len(self.desired_fields) + + +@pytest.mark.vcr() +class TestDailyProgrammerTableBasic: + """Test the Daily Programmer table.""" + + def setup(self) -> None: + """Set up the test class.""" + self.desired_fields = { + "name", + "slug", + "text", + "category", + "initial_slack_ts", + "blocks", + "initially_posted_on", + "last_posted_on", + "posted_count", + "last_modified", + "last_modified_by", + "valid", + } + self.airtable_fields = daily_programmer_table.table_fields + + def test_daily_programmer_table_has_all_desired_fields(self) -> None: + """Ensure that the affiliation table has the desired fields.""" + for field in self.airtable_fields: + assert field in self.desired_fields + + def test_daily_programmer_table_has_correct_number_of_fields(self) -> None: + """Ensure that the number of fields in the Airtable matches the number of fields in the desired fields set.""" + assert len(self.airtable_fields) == len(self.desired_fields)