From d951f911834cbf194c7a7919bc4b72b8d8f4c7dd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 9 Dec 2023 16:57:44 +0900 Subject: [PATCH] feat: improved security --- .github/workflows/drive_to_es.yml | 27 ++ .github/workflows/pythonapp.yml | 37 -- .../publish/drive_to_es/Dockerfile | 6 +- .../publish/drive_to_es/Makefile | 12 +- .../publish/drive_to_es/README.md | 15 + .../drive_to_es/drive_to_es/__init__.py | 2 +- .../publish/drive_to_es/drive_to_es/client.py | 62 +-- .../drive_to_es/drive_to_es/entities.py | 21 +- .../publish/drive_to_es/drive_to_es/values.py | 5 +- .../publish/drive_to_es/poetry.lock | 354 ++++++++++++++++-- .../publish/drive_to_es/pyproject.toml | 9 +- .../publish/drive_to_es/tests/__init__.py | 0 .../publish/drive_to_es/tests/conftest.py | 23 +- .../publish/drive_to_es/tests/test_client.py | 5 +- .../drive_to_es/tests/test_entitites.py | 31 ++ 15 files changed, 494 insertions(+), 115 deletions(-) create mode 100644 .github/workflows/drive_to_es.yml delete mode 100644 .github/workflows/pythonapp.yml create mode 100644 household_expenses/publish/drive_to_es/README.md create mode 100644 household_expenses/publish/drive_to_es/tests/__init__.py create mode 100644 household_expenses/publish/drive_to_es/tests/test_entitites.py diff --git a/.github/workflows/drive_to_es.yml b/.github/workflows/drive_to_es.yml new file mode 100644 index 0000000..957b3e5 --- /dev/null +++ b/.github/workflows/drive_to_es.yml @@ -0,0 +1,27 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: CI/CD for drive_to_es + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11"] + poetry-version: ["1.5.1"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Test + run: | + apt install pipx && \ + pipx install poetry && \ + poetry install && \ + cd household_expenses/publish/drive_to_es/ + make test + make lint + make static-type-check \ No newline at end of file diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml deleted file mode 100644 index c49bec8..0000000 --- a/.github/workflows/pythonapp.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python application - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8, 3.9] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - # Future use - #- name: Test with pytest - # run: | - # pip install pytest - # pytest diff --git a/household_expenses/publish/drive_to_es/Dockerfile b/household_expenses/publish/drive_to_es/Dockerfile index 99b7abc..ead0509 100644 --- a/household_expenses/publish/drive_to_es/Dockerfile +++ b/household_expenses/publish/drive_to_es/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine ENV POETRY_HOME="/opt/poetry" ENV POETRY_VERSION=1.5.1 @@ -9,4 +9,6 @@ RUN python3 -m venv $POETRY_HOME \ WORKDIR /app ADD . /app -RUN $POETRY_HOME/bin/poetry install \ No newline at end of file +RUN $POETRY_HOME/bin/poetry install --without dev --sync + +ENTRYPOINt python3 __main__.py \ No newline at end of file diff --git a/household_expenses/publish/drive_to_es/Makefile b/household_expenses/publish/drive_to_es/Makefile index e387c28..e5c68ae 100644 --- a/household_expenses/publish/drive_to_es/Makefile +++ b/household_expenses/publish/drive_to_es/Makefile @@ -1,2 +1,12 @@ + +test: + pytest -s tests/ + +lint: + pylint --extension-pkg-whitelist=pydantic drive_to_es/ + +static-type-check: + mypy drive_to_es + build: - sudo docker build -t sheets-to-es:latest . + sudo docker build -t drive-to-es:latest . diff --git a/household_expenses/publish/drive_to_es/README.md b/household_expenses/publish/drive_to_es/README.md new file mode 100644 index 0000000..6ec488c --- /dev/null +++ b/household_expenses/publish/drive_to_es/README.md @@ -0,0 +1,15 @@ + +## Build + +```shell +make build +``` + +## Run + +```shell +docker run \ + -e ES_HOST=YOUR_ES_HOST \ + -e SERVICE_ACCOUNT_INFO=YOUR_SVC_ACCOUNT_INFO \ + drive_to_es:latest python3 __main__.py +``` \ No newline at end of file diff --git a/household_expenses/publish/drive_to_es/drive_to_es/__init__.py b/household_expenses/publish/drive_to_es/drive_to_es/__init__.py index bf64e68..3c2c858 100644 --- a/household_expenses/publish/drive_to_es/drive_to_es/__init__.py +++ b/household_expenses/publish/drive_to_es/drive_to_es/__init__.py @@ -3,4 +3,4 @@ import logging logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) \ No newline at end of file +logger.setLevel(logging.INFO) diff --git a/household_expenses/publish/drive_to_es/drive_to_es/client.py b/household_expenses/publish/drive_to_es/drive_to_es/client.py index 18bcea8..587c5cd 100644 --- a/household_expenses/publish/drive_to_es/drive_to_es/client.py +++ b/household_expenses/publish/drive_to_es/drive_to_es/client.py @@ -1,52 +1,46 @@ +""" Client module """ from typing import List, Dict, Any, Generator - -from google.oauth2 import service_account -from googleapiclient.discovery import build -from googleapiclient.http import MediaIoBaseDownload from io import BytesIO, StringIO -from elasticsearch import Elasticsearch -from elasticsearch.helpers import bulk import csv import logging -import os from hashlib import md5 from datetime import datetime -from .entities import IncomeByDateEntity -from .entities import IncomeByItemEntity -from .values import LabelValue +from elasticsearch import Elasticsearch +from elasticsearch.helpers import bulk +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaIoBaseDownload +from drive_to_es.entities import IncomeByDateEntity, IncomeByItemEntity, Env +from drive_to_es.values import LabelValue SCOPES = ['https://www.googleapis.com/auth/drive'] LOGGER = logging.getLogger(__name__) def run(): - - es_host = os.getenv('ES_HOST', ['localhost:9200']) - service_account_file = os.getenv('SERVICE_ACCOUNT_FILE') - upload_file_name = os.getenv( - 'UPLOAD_FILE_NAME', - f'income-{str(datetime.now().year)}.csv') - + """ Client operations """ # Prepare credential LOGGER.info('Prepare credential.') - credentials = service_account.Credentials.from_service_account_file( - service_account_file, scopes=SCOPES) + env: Env = Env() + credentials = service_account.Credentials.from_service_account_info( + env.service_account_info, scopes=SCOPES) - service = build('drive', 'v3', credentials=credentials) + service: Any = build('drive', 'v3', credentials=credentials) # Access drive LOGGER.info('Get file ids from drive.') - results = service.files().list(\ - orderBy='modifiedTime desc',\ - q=f"name='{upload_file_name}'",\ - fields="nextPageToken, files(id, name, modifiedTime)").execute() + results = service.files().list( # pylint: disable=maybe-no-member + orderBy='modifiedTime desc', + q=f"name='{env.upload_file_name}'", + fields="nextPageToken, files(id, name, modifiedTime)")\ + .execute() LOGGER.info(dir(results)) items = results.get('files', []) LOGGER.info(items) - request = service.files().get_media(fileId=items[0]['id']) + request = service.files().get_media(fileId=items[0]['id']) # pylint: disable=maybe-no-member fh = BytesIO() downloader = MediaIoBaseDownload(fh, request) @@ -54,14 +48,14 @@ def run(): while done is False: status, done = downloader.next_chunk() - LOGGER.info("Download %d%%." % int(status.progress() * 100)) + LOGGER.info("Download %d%%.", int(status.progress() * 100)) # Write to elasticsearch LOGGER.info('Send to elasticsearch.') fh = StringIO(fh.getvalue().decode(), newline='') data = list(csv.DictReader(fh)) - es_inst = Elasticsearch(hosts=es_host) + es_inst = Elasticsearch(hosts=env.es_host) bulk( es_inst, @@ -76,15 +70,21 @@ def run(): ) -def construct_esdoc_by_date(msgs: List[Dict[str, Any]], index: str) -> Generator[Dict[str, Any], None, None]: +def construct_esdoc_by_date( + msgs: List[Dict[str, Any]], + index: str) -> Generator[Dict[str, Any], None, None]: + """ Return Elasticsearch document """ for m in msgs: doc_id = md5(m['report_date'].encode('utf-8')).hexdigest() body = IncomeByDateEntity(updated_on=datetime.utcnow(), **m).dict() - yield dict(_id=doc_id, _op_type='index', _index=index, **body) + yield {"_id": doc_id, "_op_type": "index", "_index": index, **body} -def construct_esdoc_by_item(msgs: List[Dict[str, Any]], index: str) -> Generator[Dict[str, Any], None, None]: +def construct_esdoc_by_item( + msgs: List[Dict[str, Any]], + index: str) -> Generator[Dict[str, Any], None, None]: + """ Return Elasticsearch document """ for m in msgs: keys = filter(lambda x: x != 'report_date', m.keys()) @@ -100,4 +100,4 @@ def construct_esdoc_by_item(msgs: List[Dict[str, Any]], index: str) -> Generator item_value=m[k], item_labels=item_label).dict() - yield dict(_id=doc_id, _op_type='index', _index=index, **body) + yield {"_id": doc_id, "_op_type": "index", "_index": index, **body} diff --git a/household_expenses/publish/drive_to_es/drive_to_es/entities.py b/household_expenses/publish/drive_to_es/drive_to_es/entities.py index 5c632db..911ba5b 100644 --- a/household_expenses/publish/drive_to_es/drive_to_es/entities.py +++ b/household_expenses/publish/drive_to_es/drive_to_es/entities.py @@ -1,8 +1,26 @@ +""" Entity module """ from datetime import datetime -from pydantic import BaseModel +from typing import Any +from pydantic import BaseModel, AnyHttpUrl, Json, BaseSettings + +# pylint: disable=too-few-public-methods + + +class Env(BaseSettings): + """ + Attributes + ---------- + es_host: http/https url for elasticsearch host + service_account_info; A valid API Key for Service Account + upload_file_name: The income statement csv file name + """ + es_host: AnyHttpUrl + service_account_info: Json[Any] # pylint: disable=unsubscriptable-object + upload_file_name: str = f'income-{str(datetime.now().year)}.csv' class IncomeByDateEntity(BaseModel): + """ Entity which holds income info by date """ report_date: str updated_on: datetime income_tax: int = 0 @@ -39,6 +57,7 @@ class IncomeByDateEntity(BaseModel): class IncomeByItemEntity(BaseModel): + """ Entity which holds income info by income attribute """ report_date: str updated_on: datetime item_key: str diff --git a/household_expenses/publish/drive_to_es/drive_to_es/values.py b/household_expenses/publish/drive_to_es/drive_to_es/values.py index eb93ca3..d44ee2f 100644 --- a/household_expenses/publish/drive_to_es/drive_to_es/values.py +++ b/household_expenses/publish/drive_to_es/drive_to_es/values.py @@ -1,8 +1,12 @@ +""" Values """ from pydantic import BaseModel from pydantic import Field +# pylint: disable=too-few-public-methods + class LabelValue(BaseModel): + """ Labels for each key """ report_date: list = Field([''], const=True) updated_on: list = Field([''], const=True) income_tax: list = Field(['withholding_tax'], const=True) @@ -36,4 +40,3 @@ class LabelValue(BaseModel): creditcard_view: list = Field(['cashout','creditcard'], const=True) creditcard_mc: list = Field(['cashout','creditcard'], const=True) basic_life: list = Field(['cashout'], const=True) - diff --git a/household_expenses/publish/drive_to_es/poetry.lock b/household_expenses/publish/drive_to_es/poetry.lock index c1c53c5..b4d913a 100644 --- a/household_expenses/publish/drive_to_es/poetry.lock +++ b/household_expenses/publish/drive_to_es/poetry.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "astroid" +version = "3.0.1" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.0.1-py3-none-any.whl", hash = "sha256:7d5895c9825e18079c5aeac0572bc2e4c83205c95d416e0b4fee8bc361d2d9ca"}, + {file = "astroid-3.0.1.tar.gz", hash = "sha256:86b0bb7d7da0be1a7c4aedb7974e391b32d4ed89e33de6ed6902b4b15c97577e"}, +] + [[package]] name = "cachetools" version = "5.3.2" @@ -132,6 +143,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +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] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "elasticsearch" version = "7.17.9" @@ -153,29 +178,15 @@ develop = ["black", "coverage", "jinja2", "mock", "pytest", "pytest-cov", "pyyam docs = ["sphinx (<1.7)", "sphinx-rtd-theme"] requests = ["requests (>=2.4.0,<3.0.0)"] -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "google-api-core" -version = "2.14.0" +version = "2.15.0" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.14.0.tar.gz", hash = "sha256:5368a4502b793d9bbf812a5912e13e4e69f9bd87f6efb508460c43f5bbd1ce41"}, - {file = "google_api_core-2.14.0-py3-none-any.whl", hash = "sha256:de2fb50ed34d47ddbb2bd2dcf680ee8fead46279f4ed6b16de362aca23a18952"}, + {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, + {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, ] [package.dependencies] @@ -207,15 +218,31 @@ google-auth-httplib2 = ">=0.1.0" httplib2 = ">=0.15.0,<1.dev0" uritemplate = ">=3.0.1,<5" +[[package]] +name = "google-api-python-client-stubs" +version = "1.23.0" +description = "Type stubs for google-api-python-client" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "google_api_python_client_stubs-1.23.0-py3-none-any.whl", hash = "sha256:749a1f539afea6faa9528a237ae3e07097ed3e3dec57999555f627b6f29b0b3d"}, + {file = "google_api_python_client_stubs-1.23.0.tar.gz", hash = "sha256:216b05b4244758a16c347ad385b4cab1266168e1ed6cd28bfa193b7d33f36505"}, +] + +[package.dependencies] +google-api-python-client = ">=2.107.0" +types-httplib2 = ">=0.22.0.2" +typing-extensions = ">=3.10.0" + [[package]] name = "google-auth" -version = "2.23.4" +version = "2.25.2" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.23.4.tar.gz", hash = "sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3"}, - {file = "google_auth-2.23.4-py2.py3-none-any.whl", hash = "sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2"}, + {file = "google-auth-2.25.2.tar.gz", hash = "sha256:42f707937feb4f5e5a39e6c4f343a17300a459aaf03141457ba505812841cc40"}, + {file = "google_auth-2.25.2-py2.py3-none-any.whl", hash = "sha256:473a8dfd0135f75bb79d878436e568f2695dce456764bf3a02b6f8c540b1d256"}, ] [package.dependencies] @@ -263,15 +290,31 @@ requests-oauthlib = ">=0.7.0" [package.extras] tool = ["click (>=6.0.0)"] +[[package]] +name = "google-auth-stubs" +version = "0.2.0" +description = "Type stubs for google-auth" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "google-auth-stubs-0.2.0.tar.gz", hash = "sha256:96085d3b7f704f44231a19683a57f7e77e964168ad2251302beae9136405d0d7"}, + {file = "google_auth_stubs-0.2.0-py3-none-any.whl", hash = "sha256:98ed66dfd75687569c257a0cbb09c0f614eed22106955edcb2f05c6617899c6a"}, +] + +[package.dependencies] +google-auth = ">=2.7.0,<3.0.0" +grpc-stubs = ">=1.24.7,<2.0.0" +types-requests = ">=2.25.9,<3.0.0" + [[package]] name = "googleapis-common-protos" -version = "1.61.0" +version = "1.62.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.61.0.tar.gz", hash = "sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b"}, - {file = "googleapis_common_protos-1.61.0-py2.py3-none-any.whl", hash = "sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0"}, + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, ] [package.dependencies] @@ -280,6 +323,86 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "grpc-stubs" +version = "1.53.0.3" +description = "Mypy stubs for gRPC" +optional = false +python-versions = ">=3.6" +files = [ + {file = "grpc-stubs-1.53.0.3.tar.gz", hash = "sha256:6e5d75cdc88c0ba918e2f8395851f1e6a7c19a7c7fc3e902bde4601c7a1cbf96"}, + {file = "grpc_stubs-1.53.0.3-py3-none-any.whl", hash = "sha256:312c3c697089344936c9779118a105bcc4ccc8eef053265f3f23086acdba2683"}, +] + +[package.dependencies] +grpcio = "*" + +[[package]] +name = "grpcio" +version = "1.60.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.60.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:d020cfa595d1f8f5c6b343530cd3ca16ae5aefdd1e832b777f9f0eb105f5b139"}, + {file = "grpcio-1.60.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b98f43fcdb16172dec5f4b49f2fece4b16a99fd284d81c6bbac1b3b69fcbe0ff"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:20e7a4f7ded59097c84059d28230907cd97130fa74f4a8bfd1d8e5ba18c81491"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452ca5b4afed30e7274445dd9b441a35ece656ec1600b77fff8c216fdf07df43"}, + {file = "grpcio-1.60.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43e636dc2ce9ece583b3e2ca41df5c983f4302eabc6d5f9cd04f0562ee8ec1ae"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e306b97966369b889985a562ede9d99180def39ad42c8014628dd3cc343f508"}, + {file = "grpcio-1.60.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f897c3b127532e6befdcf961c415c97f320d45614daf84deba0a54e64ea2457b"}, + {file = "grpcio-1.60.0-cp310-cp310-win32.whl", hash = "sha256:b87efe4a380887425bb15f220079aa8336276398dc33fce38c64d278164f963d"}, + {file = "grpcio-1.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:a9c7b71211f066908e518a2ef7a5e211670761651039f0d6a80d8d40054047df"}, + {file = "grpcio-1.60.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:fb464479934778d7cc5baf463d959d361954d6533ad34c3a4f1d267e86ee25fd"}, + {file = "grpcio-1.60.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4b44d7e39964e808b071714666a812049765b26b3ea48c4434a3b317bac82f14"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:90bdd76b3f04bdb21de5398b8a7c629676c81dfac290f5f19883857e9371d28c"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91229d7203f1ef0ab420c9b53fe2ca5c1fbeb34f69b3bc1b5089466237a4a134"}, + {file = "grpcio-1.60.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b36a2c6d4920ba88fa98075fdd58ff94ebeb8acc1215ae07d01a418af4c0253"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:297eef542156d6b15174a1231c2493ea9ea54af8d016b8ca7d5d9cc65cfcc444"}, + {file = "grpcio-1.60.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:87c9224acba0ad8bacddf427a1c2772e17ce50b3042a789547af27099c5f751d"}, + {file = "grpcio-1.60.0-cp311-cp311-win32.whl", hash = "sha256:95ae3e8e2c1b9bf671817f86f155c5da7d49a2289c5cf27a319458c3e025c320"}, + {file = "grpcio-1.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:467a7d31554892eed2aa6c2d47ded1079fc40ea0b9601d9f79204afa8902274b"}, + {file = "grpcio-1.60.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:a7152fa6e597c20cb97923407cf0934e14224af42c2b8d915f48bc3ad2d9ac18"}, + {file = "grpcio-1.60.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:7db16dd4ea1b05ada504f08d0dca1cd9b926bed3770f50e715d087c6f00ad748"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:b0571a5aef36ba9177e262dc88a9240c866d903a62799e44fd4aae3f9a2ec17e"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fd9584bf1bccdfff1512719316efa77be235469e1e3295dce64538c4773840b"}, + {file = "grpcio-1.60.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6a478581b1a1a8fdf3318ecb5f4d0cda41cacdffe2b527c23707c9c1b8fdb55"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:77c8a317f0fd5a0a2be8ed5cbe5341537d5c00bb79b3bb27ba7c5378ba77dbca"}, + {file = "grpcio-1.60.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1c30bb23a41df95109db130a6cc1b974844300ae2e5d68dd4947aacba5985aa5"}, + {file = "grpcio-1.60.0-cp312-cp312-win32.whl", hash = "sha256:2aef56e85901c2397bd557c5ba514f84de1f0ae5dd132f5d5fed042858115951"}, + {file = "grpcio-1.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:e381fe0c2aa6c03b056ad8f52f8efca7be29fb4d9ae2f8873520843b6039612a"}, + {file = "grpcio-1.60.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:92f88ca1b956eb8427a11bb8b4a0c0b2b03377235fc5102cb05e533b8693a415"}, + {file = "grpcio-1.60.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:e278eafb406f7e1b1b637c2cf51d3ad45883bb5bd1ca56bc05e4fc135dfdaa65"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:a48edde788b99214613e440fce495bbe2b1e142a7f214cce9e0832146c41e324"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de2ad69c9a094bf37c1102b5744c9aec6cf74d2b635558b779085d0263166454"}, + {file = "grpcio-1.60.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:073f959c6f570797272f4ee9464a9997eaf1e98c27cb680225b82b53390d61e6"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c826f93050c73e7769806f92e601e0efdb83ec8d7c76ddf45d514fee54e8e619"}, + {file = "grpcio-1.60.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e30be89a75ee66aec7f9e60086fadb37ff8c0ba49a022887c28c134341f7179"}, + {file = "grpcio-1.60.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b0fb2d4801546598ac5cd18e3ec79c1a9af8b8f2a86283c55a5337c5aeca4b1b"}, + {file = "grpcio-1.60.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:9073513ec380434eb8d21970e1ab3161041de121f4018bbed3146839451a6d8e"}, + {file = "grpcio-1.60.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:74d7d9fa97809c5b892449b28a65ec2bfa458a4735ddad46074f9f7d9550ad13"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:1434ca77d6fed4ea312901122dc8da6c4389738bf5788f43efb19a838ac03ead"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e61e76020e0c332a98290323ecfec721c9544f5b739fab925b6e8cbe1944cf19"}, + {file = "grpcio-1.60.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675997222f2e2f22928fbba640824aebd43791116034f62006e19730715166c0"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5208a57eae445ae84a219dfd8b56e04313445d146873117b5fa75f3245bc1390"}, + {file = "grpcio-1.60.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:428d699c8553c27e98f4d29fdc0f0edc50e9a8a7590bfd294d2edb0da7be3629"}, + {file = "grpcio-1.60.0-cp38-cp38-win32.whl", hash = "sha256:83f2292ae292ed5a47cdcb9821039ca8e88902923198f2193f13959360c01860"}, + {file = "grpcio-1.60.0-cp38-cp38-win_amd64.whl", hash = "sha256:705a68a973c4c76db5d369ed573fec3367d7d196673fa86614b33d8c8e9ebb08"}, + {file = "grpcio-1.60.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c193109ca4070cdcaa6eff00fdb5a56233dc7610216d58fb81638f89f02e4968"}, + {file = "grpcio-1.60.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:676e4a44e740deaba0f4d95ba1d8c5c89a2fcc43d02c39f69450b1fa19d39590"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5ff21e000ff2f658430bde5288cb1ac440ff15c0d7d18b5fb222f941b46cb0d2"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c86343cf9ff7b2514dd229bdd88ebba760bd8973dac192ae687ff75e39ebfab"}, + {file = "grpcio-1.60.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fd3b3968ffe7643144580f260f04d39d869fcc2cddb745deef078b09fd2b328"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:30943b9530fe3620e3b195c03130396cd0ee3a0d10a66c1bee715d1819001eaf"}, + {file = "grpcio-1.60.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b10241250cb77657ab315270b064a6c7f1add58af94befa20687e7c8d8603ae6"}, + {file = "grpcio-1.60.0-cp39-cp39-win32.whl", hash = "sha256:79a050889eb8d57a93ed21d9585bb63fca881666fc709f5d9f7f9372f5e7fd03"}, + {file = "grpcio-1.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a97a681e82bc11a42d4372fe57898d270a2707f36c45c6676e49ce0d5c41353"}, + {file = "grpcio-1.60.0.tar.gz", hash = "sha256:2199165a1affb666aa24adf0c97436686d0a61bc5fc113c037701fb7c7fceb96"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.60.0)"] + [[package]] name = "httplib2" version = "0.22.0" @@ -296,13 +419,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "3.5" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.5-py3-none-any.whl", hash = "sha256:79b8f0ac92d2351be5f6122356c9a592c96d81c9a79e4b488bf2a6a15f88057a"}, - {file = "idna-3.5.tar.gz", hash = "sha256:27009fe2735bf8723353582d48575b23c533cc2c2de7b5a68908d91b5eb18d08"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -316,6 +439,91 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +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] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +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 = "mypy" +version = "1.7.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, + {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, + {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, + {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, + {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, + {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, + {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, + {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, + {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, + {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, + {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, + {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, + {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, + {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, + {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, + {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, + {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, + {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +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 = "oauthlib" version = "3.2.2" @@ -343,6 +551,21 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "platformdirs" +version = "4.1.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[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" @@ -455,6 +678,33 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pylint" +version = "3.0.2" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.0.2-py3-none-any.whl", hash = "sha256:60ed5f3a9ff8b61839ff0348b3624ceeb9e6c2a92c514d81c9cc273da3b6bcda"}, + {file = "pylint-3.0.2.tar.gz", hash = "sha256:0d4c286ef6d2f66c8bfb527a7f8a629009e42c99707dec821a03e1b51a4c1496"}, +] + +[package.dependencies] +astroid = ">=3.0.1,<=3.1.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, +] +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pyparsing" version = "3.1.1" @@ -482,11 +732,9 @@ files = [ [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"] @@ -545,14 +793,50 @@ files = [ pyasn1 = ">=0.1.3" [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" +name = "tomlkit" +version = "0.12.3" +description = "Style preserving TOML library" optional = false 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"}, + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, +] + +[[package]] +name = "types-httplib2" +version = "0.22.0.2" +description = "Typing stubs for httplib2" +optional = false +python-versions = "*" +files = [ + {file = "types-httplib2-0.22.0.2.tar.gz", hash = "sha256:650ff0eaa6e34778cdcc14946000cdfde70f5adf015f8a1a3ddb41225606d53a"}, + {file = "types_httplib2-0.22.0.2-py3-none-any.whl", hash = "sha256:d3fbbcf4980d9e5493a7c0bb813038a96b89516d9af44f3829aaad6c01bfdd5a"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.6" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, + {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.14" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, + {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, ] [[package]] @@ -595,5 +879,5 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "caafd94adb6b122e5c2423fc6c87e8a201d67074554aa41cb27bf54d88682f36" +python-versions = "^3.11" +content-hash = "169543dd6cd887571df857805c3767f1c4edafcceb0ecbc353e5ac7479d5a691" diff --git a/household_expenses/publish/drive_to_es/pyproject.toml b/household_expenses/publish/drive_to_es/pyproject.toml index 7406ec8..ab67835 100644 --- a/household_expenses/publish/drive_to_es/pyproject.toml +++ b/household_expenses/publish/drive_to_es/pyproject.toml @@ -5,13 +5,20 @@ description = "" authors = ["Yu Watanabe "] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.11" google-api-python-client = "~2.108.0" google-auth-httplib2 = "~0.1.1" google-auth-oauthlib = "~1.1.0" elasticsearch = "~7.17.9" pydantic = "~1.10.13" + + +[tool.poetry.group.dev.dependencies] pytest = "~7.4.3" +pylint = "~3.0.2" +mypy = "~=1.7.1" +google-api-python-client-stubs = "~=1.23.0" +google-auth-stubs = "~=0.2.0" [build-system] diff --git a/household_expenses/publish/drive_to_es/tests/__init__.py b/household_expenses/publish/drive_to_es/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/household_expenses/publish/drive_to_es/tests/conftest.py b/household_expenses/publish/drive_to_es/tests/conftest.py index bde0e1f..699fad6 100644 --- a/household_expenses/publish/drive_to_es/tests/conftest.py +++ b/household_expenses/publish/drive_to_es/tests/conftest.py @@ -1,11 +1,30 @@ import csv +from pathlib import Path from typing import Dict, Any import pytest -@pytest.fixture +PATH_TO_FIXTURE = f'{str(Path(__file__).resolve().parent.absolute())}/fixtures' + + +@pytest.fixture(scope="function") +def env(monkeypatch): + monkeypatch.setenv("ES_HOST", "http://elasticsearch:9200") + monkeypatch.setenv("SERVICE_ACCOUNT_INFO", '{"type":"service_account",' \ + '"project_id":"my_project_id",' \ + '"private_key_id":"abcdefgh",' \ + '"private_key":"the_private_key",' \ + '"client_email":"dev-xx@hoge.iam.gserviceaccount.com",' \ + '"client_id":"106313935326358400493",' \ + '"auth_uri":"https://accounts.google.com/o/oauth2/auth",' \ + '"token_uri":"https://oauth2.googleapis.com/token",' \ + '"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",' \ + '"client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/dev-xx%40hoge.iam.gserviceaccount.com"}') + + +@pytest.fixture(scope="function") def single_month_report_as_dict(): - with open('fixtures/report.csv') as csvfile: + with open(f'{PATH_TO_FIXTURE}/report.csv') as csvfile: reader: csv.DictReader[Any] = csv.DictReader(csvfile) type_inferred: Dict[str, Any] = {} diff --git a/household_expenses/publish/drive_to_es/tests/test_client.py b/household_expenses/publish/drive_to_es/tests/test_client.py index 9d4c4f9..3697b43 100644 --- a/household_expenses/publish/drive_to_es/tests/test_client.py +++ b/household_expenses/publish/drive_to_es/tests/test_client.py @@ -1,9 +1,8 @@ +from drive_to_es.client import construct_esdoc_by_date from pathlib import Path from typing import List, Any, Dict -import sys -sys.path.append(str(Path(__file__).resolve().parent.parent.absolute())) -from pkgs.client import construct_esdoc_by_date, construct_esdoc_by_item +PATH_TO_FIXTURE = f'{str(Path(__file__).resolve().parent.absolute())}/fixtures' def test_construct_esdoc_by_date(single_month_report_as_dict): diff --git a/household_expenses/publish/drive_to_es/tests/test_entitites.py b/household_expenses/publish/drive_to_es/tests/test_entitites.py new file mode 100644 index 0000000..50bcb17 --- /dev/null +++ b/household_expenses/publish/drive_to_es/tests/test_entitites.py @@ -0,0 +1,31 @@ +import pytest + +from pydantic import ValidationError +from drive_to_es.entities import Env + + +def test_env_entity(env): + env = Env() + + assert isinstance(env, Env) + + +def test_invalid_es_host(): + env_attrs = { + "es_host": "localhost:9200", + "service_account_info": '{"key", "value"}' + } + + with pytest.raises(ValidationError): + Env(**env_attrs) + + +def test_invalid_service_account_info(): + env_attrs = { + "es_host": "http://localhost:9200", + "service_account_info": "hoge" + } + + with pytest.raises(ValidationError): + Env(**env_attrs) +