From 4acf52bb1f260743da59c0a9f94f8fe6cc7e4fa7 Mon Sep 17 00:00:00 2001 From: Benjamin Dornel Date: Mon, 30 Oct 2023 23:13:55 +0800 Subject: [PATCH] refactor!: remove cloud code from monopoly --- Dockerfile | 2 +- Makefile | 20 - docker-compose.yaml | 1 - monopoly/config.py | 6 - monopoly/constants.py | 5 - monopoly/gmail/__init__.py | 1 - monopoly/gmail/credentials.py | 50 -- monopoly/gmail/exceptions.py | 10 - monopoly/gmail/gmail.py | 193 ------- monopoly/gmail/pubsub.py | 37 -- monopoly/main.py | 49 -- monopoly/processor.py | 17 +- monopoly/storage/__init__.py | 4 +- monopoly/storage/storage.py | 18 - poetry.lock | 608 +--------------------- pyproject.toml | 21 +- terraform/api.tf | 56 -- terraform/bigquery.tf | 25 - terraform/data.tf | 4 - terraform/local.tf | 4 - terraform/main.tf | 109 ---- terraform/providers.tf | 13 - terraform/pubsub.tf | 19 - terraform/variables.tf | 38 -- tests/conftest.py | 11 - tests/integration/test_main_entrypoint.py | 45 -- tests/unit/test_check_trusted_user.py | 27 - tests/unit/test_generate_name.py | 36 -- tests/unit/test_get_attachment.py | 71 --- tests/unit/test_persist_attachment.py | 40 -- 30 files changed, 15 insertions(+), 1525 deletions(-) delete mode 100644 Makefile delete mode 100644 monopoly/gmail/__init__.py delete mode 100644 monopoly/gmail/credentials.py delete mode 100644 monopoly/gmail/exceptions.py delete mode 100644 monopoly/gmail/gmail.py delete mode 100644 monopoly/gmail/pubsub.py delete mode 100644 monopoly/main.py delete mode 100644 terraform/api.tf delete mode 100644 terraform/bigquery.tf delete mode 100644 terraform/data.tf delete mode 100644 terraform/local.tf delete mode 100644 terraform/main.tf delete mode 100644 terraform/providers.tf delete mode 100644 terraform/pubsub.tf delete mode 100644 terraform/variables.tf delete mode 100644 tests/integration/test_main_entrypoint.py delete mode 100644 tests/unit/test_check_trusted_user.py delete mode 100644 tests/unit/test_generate_name.py delete mode 100644 tests/unit/test_get_attachment.py delete mode 100644 tests/unit/test_persist_attachment.py diff --git a/Dockerfile b/Dockerfile index 44455f14..3e53c4f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ FROM base AS test RUN apt-get update \ && apt-get -y install build-essential libpoppler-cpp-dev pkg-config -RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --no-root +RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --without dev --no-root COPY --from=brew /home/linuxbrew/.linuxbrew/Cellar /home/linuxbrew/.linuxbrew/Cellar COPY --from=brew /home/linuxbrew/.linuxbrew/bin/john /home/linuxbrew/.linuxbrew/bin/john diff --git a/Makefile b/Makefile deleted file mode 100644 index a3fd1182..00000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -image := monopoly -repository := monopoly -project := phonic-ceremony-394407 -docker_url := us-central1-docker.pkg.dev - -default: build push - -build: - docker compose build - -push: - docker tag ${image} \ - ${docker_url}/${project}/${repository}/${image}:main - - docker push ${docker_url}/${project}/${repository}/${image}:main - @echo "Successfully uploaded image" - -docker-test: - docker build -t monopoly-test --target test . - docker run --rm monopoly-test diff --git a/docker-compose.yaml b/docker-compose.yaml index 482d68a7..9253ab83 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,7 +5,6 @@ services: container_name: monopoly image: monopoly volumes: - - $HOME/.config/gcloud:/root/.config/gcloud - ./monopoly:/monopoly env_file: .env diff --git a/monopoly/config.py b/monopoly/config.py index 08ad59c8..38e76c86 100644 --- a/monopoly/config.py +++ b/monopoly/config.py @@ -8,16 +8,10 @@ class Settings(BaseSettings): - gmail_address: str = "" - project_id: str = "" - pubsub_topic: str = "" - secret_id: str = "" - gcs_bucket: str = "" ocbc_pdf_password: str = "" citibank_pdf_password: str = "" standard_chartered_pdf_password: str = "" hsbc_pdf_password_prefix: str = "" - trusted_user_emails: list = [] model_config = SettingsConfigDict(env_file=".env", extra="allow") diff --git a/monopoly/constants.py b/monopoly/constants.py index 1acaed58..79ddc409 100644 --- a/monopoly/constants.py +++ b/monopoly/constants.py @@ -33,11 +33,6 @@ class StatementFields(AutoEnum): AMOUNT = auto() -class EmailSubjectRegex(StrEnum): - OCBC = r"OCBC Bank: Your Credit Card e-Statement" - HSBC = r"Your.HSBC.*eStatement" - - class SharedPatterns(StrEnum): AMOUNT = r"(?P[\d.,]+)$" DESCRIPTION = r"(?P.*?)\s+" diff --git a/monopoly/gmail/__init__.py b/monopoly/gmail/__init__.py deleted file mode 100644 index 2bff92bf..00000000 --- a/monopoly/gmail/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .gmail import Gmail, Message, MessageAttachment, MessagePart # noqa: F401 diff --git a/monopoly/gmail/credentials.py b/monopoly/gmail/credentials.py deleted file mode 100644 index 562f6664..00000000 --- a/monopoly/gmail/credentials.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -import json -import logging -from typing import TYPE_CHECKING - -from google.cloud import secretmanager -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build - -from monopoly.config import settings - -if TYPE_CHECKING: - from googleapiclient._apis.gmail.v1.resources import GmailResource - - -logger = logging.getLogger(__name__) - - -def get_credentials(version_id="latest"): - project_id = settings.project_id - secret_id = settings.secret_id - scopes = [ - "https://www.googleapis.com/auth/gmail.readonly", - "https://www.googleapis.com/auth/gmail.settings.basic", - "https://www.googleapis.com/auth/gmail.labels", - "https://www.googleapis.com/auth/gmail.modify", - ] - - client = secretmanager.SecretManagerServiceClient() - name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}" - response = client.access_secret_version(name=name) - - credentials = Credentials.from_authorized_user_info( - info=json.loads(response.payload.data), scopes=scopes - ) - return credentials - - -def get_gmail_service() -> GmailResource: - logger.info("Creating Gmail client") - credentials = get_credentials() - - try: - service = build("gmail", "v1", credentials=credentials, cache_discovery=False) - return service - - except Exception as err: - logger.error(err) - raise err diff --git a/monopoly/gmail/exceptions.py b/monopoly/gmail/exceptions.py deleted file mode 100644 index e7a11db8..00000000 --- a/monopoly/gmail/exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -class MultipleAttachmentsError(Exception): - """Raise when an email has more than one attachment""" - - -class UntrustedUserError(Exception): - """Raise when email received from an untrusted user""" - - -class AttachmentNotFoundError(Exception): - """Raise when attachment not found in email""" diff --git a/monopoly/gmail/gmail.py b/monopoly/gmail/gmail.py deleted file mode 100644 index e57a173b..00000000 --- a/monopoly/gmail/gmail.py +++ /dev/null @@ -1,193 +0,0 @@ -from __future__ import annotations - -import contextlib -import logging -import os -import sys -from base64 import urlsafe_b64decode -from dataclasses import dataclass -from tempfile import TemporaryDirectory -from typing import TYPE_CHECKING - -from monopoly.config import settings -from monopoly.gmail.credentials import get_gmail_service -from monopoly.gmail.exceptions import ( - AttachmentNotFoundError, - MultipleAttachmentsError, - UntrustedUserError, -) - -if TYPE_CHECKING: - from googleapiclient._apis.gmail.v1.resources import GmailResource - -logger = logging.getLogger(__name__) - - -class Gmail: - def __init__(self, gmail_service: GmailResource = None): - if not gmail_service: - self.gmail_service = get_gmail_service() - - def get_emails(self, query="is:unread", latest=False) -> list[Message]: - emails: list = ( - self.gmail_service.users() - .messages() - .list(userId="me", q=query) - .execute() - .get("messages") - ) - if not emails: - logger.info("No emails found using query: '%s'", query) - sys.exit(0) - - if latest: - emails = [emails[0]] - - messages = [] - for email in emails: - email_id = email["id"] - logger.info("Retrieving email %s", email_id) - message = ( - self.gmail_service.users() - .messages() - .get(userId="me", id=email_id) - .execute() - ) - messages.append(message) - - return [Message(message, self.gmail_service) for message in messages] - - def get_attachment_byte_string(self, message_id, attachment_id) -> dict: - logger.debug("Extracting attachment byte string") - attachment = ( - self.gmail_service.users() - .messages() - .attachments() - .get(userId="me", messageId=message_id, id=attachment_id) - .execute() - ) - data: bytes = urlsafe_b64decode(attachment["data"].encode("utf-8")) - return data - - -class Message(Gmail): - def __init__(self, data: dict, gmail_service: GmailResource): - self.message_id: str | None = data.get("id") - self.payload: dict | None = data.get("payload") - self.gmail_service = gmail_service - self.trusted_user_emails = settings.trusted_user_emails - super().__init__(gmail_service) - - def get_attachment(self): - attachment_part = self._get_attachment_part() - logger.info("Extracting attachment '%s'", attachment_part.filename) - - if not self.from_trusted_user: - raise UntrustedUserError("Not from trusted user") - - file_byte_string = self.get_attachment_byte_string( - self.message_id, attachment_part.attachment_id - ) - return MessageAttachment(attachment_part.filename, file_byte_string) - - @staticmethod - @contextlib.contextmanager - def save(attachment: MessageAttachment) -> TemporaryDirectory: - """Saves attachment to a temporary directory""" - temp_dir = TemporaryDirectory() - temp_file_path = os.path.join(temp_dir.name, attachment.filename) - - try: - logger.info("Writing attachment to path %s", temp_file_path) - with open(temp_file_path, "wb") as file: - file.write(attachment.file_byte_string) - - yield temp_file_path - except Exception as error: - logger.error("An error occurred while saving: %s", error) - raise - finally: - temp_dir.cleanup() - - def mark_as_read(self): - logger.info("Marking email %s as read", self.message_id) - return ( - self.gmail_service.users() - .messages() - .modify( - userId="me", id=self.message_id, body={"removeLabelIds": ["UNREAD"]} - ) - .execute() - ) - - def _get_attachment_part(self) -> MessagePart: - if not self.parts: - raise AttachmentNotFoundError - - attachments = [part for part in self.parts if part.filename] - if not attachments: - raise AttachmentNotFoundError - - if len(attachments) > 1: - raise MultipleAttachmentsError - - return attachments[0] - - @property - def subject(self) -> str: - for item in self.payload.get("headers"): - if item["name"] == "Subject": - return item["value"] - raise RuntimeError("Subject could not be found") - - @property - def parts(self) -> list[MessagePart]: - """Return parts and nested parts""" - if parts := self.payload.get("parts"): - nested_parts = [ - nested_part - for part in list(parts) - if part.get("parts") - for nested_part in part.get("parts") - ] - return [MessagePart(part) for part in parts + nested_parts] - - return None - - @property - def from_trusted_user(self) -> bool: - """Check if user is trusted""" - for item in self.payload["headers"]: - if item["name"] == "From": - for trusted_email in self.trusted_user_emails: - if f"<{trusted_email}>" in item["value"]: - return True - - logger.info("No trusted user found") - return False - - -@dataclass -class MessagePart: - def __init__(self, data: dict): - self._data = data - self.part_id: str = data.get("partId") - self.filename: str = data.get("filename") - self.body: dict = data.get("body") - - @property - def attachment_id(self): - return self.body.get("attachmentId") - - def __repr__(self): - return str(self._data) - - -@dataclass -class MessageAttachment: - def __init__(self, filename, file_byte_string): - self.filename: str = filename - self.file_byte_string: bytes = file_byte_string - - def __repr__(self): - return str(self.filename) diff --git a/monopoly/gmail/pubsub.py b/monopoly/gmail/pubsub.py deleted file mode 100644 index 359598d5..00000000 --- a/monopoly/gmail/pubsub.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -from monopoly.config import settings -from monopoly.gmail.credentials import get_gmail_service - -if TYPE_CHECKING: - from googleapiclient._apis.gmail.v1.resources import GmailResource - -logger = logging.getLogger("root") - - -def set_up_gmail_push_notifications(): - service: GmailResource = get_gmail_service() - request_body = { - "labelIds": ["INBOX"], - "topicName": f"projects/{settings.project_id}/topics/{settings.pubsub_topic}", - } - - # pylint: disable=no-member - watch = ( - service.users() - .watch(userId=settings.gmail_address, body=request_body) - .execute() - ) - - logger.info( - "Successfully set up watch request on inbox - historyId: %s", watch["historyId"] - ) - - return True - - -if __name__ == "__main__": - set_up_gmail_push_notifications() diff --git a/monopoly/main.py b/monopoly/main.py deleted file mode 100644 index 3dad1291..00000000 --- a/monopoly/main.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import re - -from monopoly.banks import Hsbc, Ocbc -from monopoly.constants import EmailSubjectRegex -from monopoly.gmail import Gmail, Message -from monopoly.processor import StatementProcessor - -logger = logging.getLogger(__name__) - - -def main(): - """ - Entrypoint for Cloud Run function that extracts bank statement, - transforms it, then loads it to disk or cloud - """ - logger.info("Beginning bank statement extraction") - - messages: list[Message] = Gmail().get_emails() - - banks = {EmailSubjectRegex.OCBC: Ocbc, EmailSubjectRegex.HSBC: Hsbc} - - for message in messages: - process_bank_statement(message, banks) - - -def process_bank_statement(message: Message, banks: dict): - """ - Process a bank statement using the provided bank class. - - If an error occurs, the statement is removed from disk - """ - subject = message.subject - - for bank_regex_pattern, bank_class in banks.items(): - if re.search(bank_regex_pattern, subject): - attachment = message.get_attachment() - - with message.save(attachment) as file: # type: ignore - processor: StatementProcessor = bank_class(file_path=file) - statement = processor.extract() - transformed_df = processor.transform(statement) - processor.load(transformed_df, statement, upload_to_cloud=True) - - message.mark_as_read() - - -if __name__ == "__main__": - main() diff --git a/monopoly/processor.py b/monopoly/processor.py index 5f036738..3724bb36 100644 --- a/monopoly/processor.py +++ b/monopoly/processor.py @@ -4,11 +4,11 @@ from pandas import DataFrame -from monopoly.config import BruteForceConfig, StatementConfig, settings +from monopoly.config import BruteForceConfig, StatementConfig from monopoly.constants import StatementFields from monopoly.pdf import PdfConfig, PdfParser from monopoly.statement import Statement -from monopoly.storage import upload_to_cloud_storage, write_to_csv +from monopoly.storage import write_to_csv logger = logging.getLogger(__name__) @@ -68,19 +68,10 @@ def _convert_date(self, row, statement_date: datetime): return f"{row_year}-{row_month:02d}-{row_day:02d}" def load( - self, - df: DataFrame, - statement: Statement, - csv_file_path: Optional[str] = None, - upload_to_cloud: bool = False, + self, df: DataFrame, statement: Statement, csv_file_path: Optional[str] = None ): csv_file_path = write_to_csv( df=df, csv_file_path=csv_file_path, statement=statement ) - if upload_to_cloud: - upload_to_cloud_storage( - statement=statement, - source_filename=csv_file_path, # type: ignore - bucket_name=settings.gcs_bucket, - ) + return csv_file_path diff --git a/monopoly/storage/__init__.py b/monopoly/storage/__init__.py index fe61555c..366113a7 100644 --- a/monopoly/storage/__init__.py +++ b/monopoly/storage/__init__.py @@ -1,3 +1,3 @@ -from .storage import generate_name, upload_to_cloud_storage, write_to_csv +from .storage import generate_name, write_to_csv -__all__ = ["generate_name", "upload_to_cloud_storage", "write_to_csv"] +__all__ = ["generate_name", "write_to_csv"] diff --git a/monopoly/storage/storage.py b/monopoly/storage/storage.py index 672aec20..e944c6d7 100644 --- a/monopoly/storage/storage.py +++ b/monopoly/storage/storage.py @@ -4,7 +4,6 @@ from typing import Optional from uuid import uuid4 -from google.cloud import storage # type: ignore from pandas import DataFrame from monopoly.config import StatementConfig @@ -37,23 +36,6 @@ def generate_name( raise ValueError("Invalid format_type") -def upload_to_cloud_storage( - source_filename: str, - bucket_name: str, - statement: Statement, -) -> None: - client = storage.Client() - logger = logging.getLogger(__name__) - bucket = client.get_bucket(bucket_name) - - blob_name = generate_name("blob", statement.config, statement.statement_date) - blob = bucket.blob(blob_name) - - logger.info(f"Attempting to upload to 'gs://{bucket_name}/{blob_name}'") - blob.upload_from_filename(source_filename) - logger.info("Uploaded to %s", blob_name) - - def write_to_csv(df: DataFrame, csv_file_path: Optional[str], statement: Statement): logger = logging.getLogger(__name__) diff --git a/poetry.lock b/poetry.lock index cba8a072..9e394da4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -81,17 +81,6 @@ 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" -optional = false -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]] name = "certifi" version = "2023.7.22" @@ -365,397 +354,6 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.1.0,<3.2.0" -[[package]] -name = "google-api-core" -version = "2.11.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-api-core-2.11.1.tar.gz", hash = "sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a"}, - {file = "google_api_core-2.11.1-py3-none-any.whl", hash = "sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-api-python-client" -version = "2.98.0" -description = "Google API Client Library for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-api-python-client-2.98.0.tar.gz", hash = "sha256:93d4f7dc70f6c8349b9dcce20b7d1610aede50af01f4cae2505596bff6bb76c7"}, - {file = "google_api_python_client-2.98.0-py2.py3-none-any.whl", hash = "sha256:6e4f50cb103d6bd5399b2f94ecec13a70033554cfbd7cc9c7a4f8fa2aacb3536"}, -] - -[package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" -google-auth = ">=1.19.0,<3.0.0.dev0" -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.17.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.17.0-py3-none-any.whl", hash = "sha256:a30da7c643d78e5de0f9ebfa52f58211fa5e98bf855d3cd44fe02a8fd30bfddc"}, - {file = "google_api_python_client_stubs-1.17.0.tar.gz", hash = "sha256:2664cb20600b0c91f02b5bfc1ea59a99906091559dbac63c8b58a05541e8f38d"}, -] - -[package.dependencies] -google-api-python-client = ">=2.86.0" -typing-extensions = ">=3.10.0" - -[[package]] -name = "google-auth" -version = "2.22.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "google-auth-2.22.0.tar.gz", hash = "sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce"}, - {file = "google_auth-2.22.0-py2.py3-none-any.whl", hash = "sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" -six = ">=1.9.0" -urllib3 = "<2.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]] -name = "google-auth-httplib2" -version = "0.1.0" -description = "Google Authentication Library: httplib2 transport" -optional = false -python-versions = "*" -files = [ - {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, - {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, -] - -[package.dependencies] -google-auth = "*" -httplib2 = ">=0.15.0" -six = "*" - -[[package]] -name = "google-auth-oauthlib" -version = "1.0.0" -description = "Google Authentication Library" -optional = false -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] -google-auth = ">=2.15.0" -requests-oauthlib = ">=0.7.0" - -[package.extras] -tool = ["click (>=6.0.0)"] - -[[package]] -name = "google-cloud-core" -version = "2.3.3" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-cloud-core-2.3.3.tar.gz", hash = "sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb"}, - {file = "google_cloud_core-2.3.3-py2.py3-none-any.whl", hash = "sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)"] - -[[package]] -name = "google-cloud-secret-manager" -version = "2.16.3" -description = "Google Cloud Secret Manager API client library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-cloud-secret-manager-2.16.3.tar.gz", hash = "sha256:6cab5cbf1927a34c586e4af90437eea3d44ff4b41b9a82ec86fcff09a5ac26ea"}, - {file = "google_cloud_secret_manager-2.16.3-py2.py3-none-any.whl", hash = "sha256:266ab8da831b3532ca197b5a30dcde0f0504761390bb26c3397403d6ff0b8e3f"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[[package]] -name = "google-cloud-storage" -version = "2.10.0" -description = "Google Cloud Storage API client library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-cloud-storage-2.10.0.tar.gz", hash = "sha256:934b31ead5f3994e5360f9ff5750982c5b6b11604dc072bc452c25965e076dc7"}, - {file = "google_cloud_storage-2.10.0-py2.py3-none-any.whl", hash = "sha256:9433cf28801671de1c80434238fb1e7e4a1ba3087470e90f70c928ea77c2b9d7"}, -] - -[package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" -google-cloud-core = ">=2.3.0,<3.0dev" -google-resumable-media = ">=2.3.2" -requests = ">=2.18.0,<3.0.0dev" - -[package.extras] -protobuf = ["protobuf (<5.0.0dev)"] - -[[package]] -name = "google-crc32c" -version = "1.5.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, - {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, - {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, - {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, - {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, - {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, - {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, - {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, - {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, - {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, - {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, - {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, - {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, - {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, - {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, - {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, - {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, - {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, - {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, - {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, - {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-resumable-media" -version = "2.6.0" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">= 3.7" -files = [ - {file = "google-resumable-media-2.6.0.tar.gz", hash = "sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7"}, - {file = "google_resumable_media-2.6.0-py2.py3-none-any.whl", hash = "sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.60.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "googleapis-common-protos-1.60.0.tar.gz", hash = "sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708"}, - {file = "googleapis_common_protos-1.60.0-py2.py3-none-any.whl", hash = "sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.12.6" -description = "IAM API client library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpc-google-iam-v1-0.12.6.tar.gz", hash = "sha256:2bc4b8fdf22115a65d751c9317329322602c39b7c86a289c9b72d228d960ef5f"}, - {file = "grpc_google_iam_v1-0.12.6-py2.py3-none-any.whl", hash = "sha256:5c10f3d8dc2d88678ab1a9b0cb5482735c5efee71e6c0cd59f872eef22913f5c"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[[package]] -name = "grpcio" -version = "1.57.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-1.57.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:092fa155b945015754bdf988be47793c377b52b88d546e45c6a9f9579ac7f7b6"}, - {file = "grpcio-1.57.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f7349786da979a94690cc5c2b804cab4e8774a3cf59be40d037c4342c906649"}, - {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:82640e57fb86ea1d71ea9ab54f7e942502cf98a429a200b2e743d8672171734f"}, - {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40b72effd4c789de94ce1be2b5f88d7b9b5f7379fe9645f198854112a6567d9a"}, - {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f708a6a17868ad8bf586598bee69abded4996b18adf26fd2d91191383b79019"}, - {file = "grpcio-1.57.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:60fe15288a0a65d5c1cb5b4a62b1850d07336e3ba728257a810317be14f0c527"}, - {file = "grpcio-1.57.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6907b1cf8bb29b058081d2aad677b15757a44ef2d4d8d9130271d2ad5e33efca"}, - {file = "grpcio-1.57.0-cp310-cp310-win32.whl", hash = "sha256:57b183e8b252825c4dd29114d6c13559be95387aafc10a7be645462a0fc98bbb"}, - {file = "grpcio-1.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7b400807fa749a9eb286e2cd893e501b110b4d356a218426cb9c825a0474ca56"}, - {file = "grpcio-1.57.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c6ebecfb7a31385393203eb04ed8b6a08f5002f53df3d59e5e795edb80999652"}, - {file = "grpcio-1.57.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:00258cbe3f5188629828363ae8ff78477ce976a6f63fb2bb5e90088396faa82e"}, - {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:23e7d8849a0e58b806253fd206ac105b328171e01b8f18c7d5922274958cc87e"}, - {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5371bcd861e679d63b8274f73ac281751d34bd54eccdbfcd6aa00e692a82cd7b"}, - {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aed90d93b731929e742967e236f842a4a2174dc5db077c8f9ad2c5996f89f63e"}, - {file = "grpcio-1.57.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe752639919aad9ffb0dee0d87f29a6467d1ef764f13c4644d212a9a853a078d"}, - {file = "grpcio-1.57.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fada6b07ec4f0befe05218181f4b85176f11d531911b64c715d1875c4736d73a"}, - {file = "grpcio-1.57.0-cp311-cp311-win32.whl", hash = "sha256:bb396952cfa7ad2f01061fbc7dc1ad91dd9d69243bcb8110cf4e36924785a0fe"}, - {file = "grpcio-1.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:e503cb45ed12b924b5b988ba9576dc9949b2f5283b8e33b21dcb6be74a7c58d0"}, - {file = "grpcio-1.57.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:fd173b4cf02b20f60860dc2ffe30115c18972d7d6d2d69df97ac38dee03be5bf"}, - {file = "grpcio-1.57.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:d7f8df114d6b4cf5a916b98389aeaf1e3132035420a88beea4e3d977e5f267a5"}, - {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:76c44efa4ede1f42a9d5b2fed1fe9377e73a109bef8675fb0728eb80b0b8e8f2"}, - {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4faea2cfdf762a664ab90589b66f416274887641ae17817de510b8178356bf73"}, - {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60b83c43faeb6d0a9831f0351d7787a0753f5087cc6fa218d78fdf38e5acef0"}, - {file = "grpcio-1.57.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b363bbb5253e5f9c23d8a0a034dfdf1b7c9e7f12e602fc788c435171e96daccc"}, - {file = "grpcio-1.57.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f1fb0fd4a1e9b11ac21c30c169d169ef434c6e9344ee0ab27cfa6f605f6387b2"}, - {file = "grpcio-1.57.0-cp37-cp37m-win_amd64.whl", hash = "sha256:34950353539e7d93f61c6796a007c705d663f3be41166358e3d88c45760c7d98"}, - {file = "grpcio-1.57.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:871f9999e0211f9551f368612460442a5436d9444606184652117d6a688c9f51"}, - {file = "grpcio-1.57.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:a8a8e560e8dbbdf29288872e91efd22af71e88b0e5736b0daf7773c1fecd99f0"}, - {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2313b124e475aa9017a9844bdc5eafb2d5abdda9d456af16fc4535408c7d6da6"}, - {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4098b6b638d9e0ca839a81656a2fd4bc26c9486ea707e8b1437d6f9d61c3941"}, - {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e5b58e32ae14658085c16986d11e99abd002ddbf51c8daae8a0671fffb3467f"}, - {file = "grpcio-1.57.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0f80bf37f09e1caba6a8063e56e2b87fa335add314cf2b78ebf7cb45aa7e3d06"}, - {file = "grpcio-1.57.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5b7a4ce8f862fe32b2a10b57752cf3169f5fe2915acfe7e6a1e155db3da99e79"}, - {file = "grpcio-1.57.0-cp38-cp38-win32.whl", hash = "sha256:9338bacf172e942e62e5889b6364e56657fbf8ac68062e8b25c48843e7b202bb"}, - {file = "grpcio-1.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:e1cb52fa2d67d7f7fab310b600f22ce1ff04d562d46e9e0ac3e3403c2bb4cc16"}, - {file = "grpcio-1.57.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fee387d2fab144e8a34e0e9c5ca0f45c9376b99de45628265cfa9886b1dbe62b"}, - {file = "grpcio-1.57.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b53333627283e7241fcc217323f225c37783b5f0472316edcaa4479a213abfa6"}, - {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f19ac6ac0a256cf77d3cc926ef0b4e64a9725cc612f97228cd5dc4bd9dbab03b"}, - {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fdf04e402f12e1de8074458549337febb3b45f21076cc02ef4ff786aff687e"}, - {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5613a2fecc82f95d6c51d15b9a72705553aa0d7c932fad7aed7afb51dc982ee5"}, - {file = "grpcio-1.57.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b670c2faa92124b7397b42303e4d8eb64a4cd0b7a77e35a9e865a55d61c57ef9"}, - {file = "grpcio-1.57.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a635589201b18510ff988161b7b573f50c6a48fae9cb567657920ca82022b37"}, - {file = "grpcio-1.57.0-cp39-cp39-win32.whl", hash = "sha256:d78d8b86fcdfa1e4c21f8896614b6cc7ee01a2a758ec0c4382d662f2a62cf766"}, - {file = "grpcio-1.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:20ec6fc4ad47d1b6e12deec5045ec3cd5402d9a1597f738263e98f490fe07056"}, - {file = "grpcio-1.57.0.tar.gz", hash = "sha256:4b089f7ad1eb00a104078bab8015b0ed0ebcb3b589e527ab009c53893fd4e613"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.57.0)"] - -[[package]] -name = "grpcio-status" -version = "1.57.0" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.6" -files = [ - {file = "grpcio-status-1.57.0.tar.gz", hash = "sha256:b098da99df1eebe58337f8f78e50df990273ccacc1226fddeb47c590e3df9e02"}, - {file = "grpcio_status-1.57.0-py3-none-any.whl", hash = "sha256:15d6af055914ebbc4ed17e55ebfb8e6bb17a45a57fea32e6af19978fb7844690"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.57.0" -protobuf = ">=4.21.6" - -[[package]] -name = "httplib2" -version = "0.22.0" -description = "A comprehensive HTTP client library." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, - {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, -] - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - [[package]] name = "idna" version = "3.4" @@ -952,22 +550,6 @@ files = [ {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, ] -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -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 = "oscrypto" version = "1.3.0" @@ -1086,73 +668,6 @@ files = [ {file = "pdftotext-2.2.2.tar.gz", hash = "sha256:2a9aa89bc62022408781b39d188fabf5a3ad1103b6630f32c4e27e395f7966ee"}, ] -[[package]] -name = "pillow" -version = "10.1.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, -] - -[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" @@ -1183,45 +698,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "proto-plus" -version = "1.22.3" -description = "Beautiful, Pythonic protocol buffers." -optional = false -python-versions = ">=3.6" -files = [ - {file = "proto-plus-1.22.3.tar.gz", hash = "sha256:fdcd09713cbd42480740d2fe29c990f7fbd885a67efc328aa8be6ee3e9f76a6b"}, - {file = "proto_plus-1.22.3-py3-none-any.whl", hash = "sha256:a49cd903bc0b6ab41f76bf65510439d56ca76f868adf0274e738bfdd096894df"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<5.0.0dev" - -[package.extras] -testing = ["google-api-core[grpc] (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "4.24.2" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "protobuf-4.24.2-cp310-abi3-win32.whl", hash = "sha256:58e12d2c1aa428ece2281cef09bbaa6938b083bcda606db3da4e02e991a0d924"}, - {file = "protobuf-4.24.2-cp310-abi3-win_amd64.whl", hash = "sha256:77700b55ba41144fc64828e02afb41901b42497b8217b558e4a001f18a85f2e3"}, - {file = "protobuf-4.24.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:237b9a50bd3b7307d0d834c1b0eb1a6cd47d3f4c2da840802cd03ea288ae8880"}, - {file = "protobuf-4.24.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:25ae91d21e3ce8d874211110c2f7edd6384816fb44e06b2867afe35139e1fd1c"}, - {file = "protobuf-4.24.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:c00c3c7eb9ad3833806e21e86dca448f46035242a680f81c3fe068ff65e79c74"}, - {file = "protobuf-4.24.2-cp37-cp37m-win32.whl", hash = "sha256:4e69965e7e54de4db989289a9b971a099e626f6167a9351e9d112221fc691bc1"}, - {file = "protobuf-4.24.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c5cdd486af081bf752225b26809d2d0a85e575b80a84cde5172a05bbb1990099"}, - {file = "protobuf-4.24.2-cp38-cp38-win32.whl", hash = "sha256:6bd26c1fa9038b26c5c044ee77e0ecb18463e957fefbaeb81a3feb419313a54e"}, - {file = "protobuf-4.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb7aa97c252279da65584af0456f802bd4b2de429eb945bbc9b3d61a42a8cd16"}, - {file = "protobuf-4.24.2-cp39-cp39-win32.whl", hash = "sha256:2b23bd6e06445699b12f525f3e92a916f2dcf45ffba441026357dea7fa46f42b"}, - {file = "protobuf-4.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:839952e759fc40b5d46be319a265cf94920174d88de31657d5622b5d8d6be5cd"}, - {file = "protobuf-4.24.2-py3-none-any.whl", hash = "sha256:3b7b170d3491ceed33f723bbf2d5a260f8a4e23843799a3906f16ef736ef251e"}, - {file = "protobuf-4.24.2.tar.gz", hash = "sha256:7fda70797ddec31ddfa3576cbdcc3ddbb6b3078b737a1a87ab9136af0570cd6e"}, -] - [[package]] name = "psutil" version = "5.9.5" @@ -1248,31 +724,6 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] -[[package]] -name = "pyasn1" -version = "0.5.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -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" -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 = "pycodestyle" version = "2.11.0" @@ -1620,20 +1071,6 @@ files = [ {file = "PyMuPDFb-1.23.3-py3-none-win_amd64.whl", hash = "sha256:89d88069cb8deb100ddcf56e1feefc7cff93ff791260325ed84551f96d3abd9f"}, ] -[[package]] -name = "pyparsing" -version = "3.1.1" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pypng" version = "0.20220715.0" @@ -1841,38 +1278,6 @@ urllib3 = ">=1.21.1,<3" 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." -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" -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 = "six" version = "1.16.0" @@ -1962,17 +1367,6 @@ 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 = "uritemplate" -version = "4.1.1" -description = "Implementation of RFC 6570 URI Templates" -optional = false -python-versions = ">=3.6" -files = [ - {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, - {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, -] - [[package]] name = "uritools" version = "4.0.2" @@ -2087,4 +1481,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "d63578394cf6e2fe18ab24e24c98c69e188257679f56702d524cdb56afb64091" +content-hash = "ac58b55352bbee75b7296f7ebd69390df4c199aa274296e9601be3b3043311de" diff --git a/pyproject.toml b/pyproject.toml index ebc144b7..ec74228e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,17 @@ [tool.poetry] -name = "monopoly" +name = "monopoly-sg" version = "0.1.0" -description = "" +description = "PDF parsing for Singaporean banks" +readme = "README.md" +repository = "https://github.com/benjamin-awd/monopoly" authors = ["benjamin-awd "] +packages = [ + { include = "monopoly" }, +] [tool.poetry.dependencies] python = "^3.11" pandas = "2.1.0" -google-api-python-client = "^2.98.0" -google-auth-oauthlib = "^1.0.0" -google-auth = "^2.22.0" -google-cloud-storage = "^2.10.0" -google-cloud-secret-manager = "^2.16.3" pydantic-settings = "^2.0.3" pymupdf = "^1.23.3" pydantic = "^2.4.2" @@ -28,9 +28,7 @@ flake8 = "^6.1.0" pytest = "^7.4.1" pytest-xdist = "^3.3.1" pysnooper = "^1.2.0" -google-api-python-client-stubs = "^1.17.0" pylint-pydantic = "^0.3.0" -pillow = "^10.1.0" mypy = "^1.6.1" @@ -69,9 +67,6 @@ filterwarnings = [ disable_error_code = [ "annotation-unchecked", ] -exclude = [ - "gmail\\.py" -] [[tool.mypy.overrides]] module = [ @@ -79,8 +74,6 @@ module = [ "pdftotext", "pdf2john", "pandas", - "google.cloud", - "google.oauth2.credentials" ] ignore_missing_imports = true diff --git a/terraform/api.tf b/terraform/api.tf deleted file mode 100644 index df114973..00000000 --- a/terraform/api.tf +++ /dev/null @@ -1,56 +0,0 @@ -resource "google_project_service" "artifactregistry" { - service = "artifactregistry.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "gmail" { - service = "gmail.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "secretmanager" { - service = "secretmanager.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -# Cloud scheduler APIs -resource "google_project_service" "cloudscheduler" { - service = "cloudscheduler.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "compute" { - service = "compute.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "logging" { - service = "logging.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "batch" { - service = "batch.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} - -resource "google_project_service" "cloudresourcemanager" { - service = "cloudresourcemanager.googleapis.com" - - disable_dependent_services = false - disable_on_destroy = false -} diff --git a/terraform/bigquery.tf b/terraform/bigquery.tf deleted file mode 100644 index a921ed3d..00000000 --- a/terraform/bigquery.tf +++ /dev/null @@ -1,25 +0,0 @@ -resource "google_bigquery_dataset" "default" { - dataset_id = "monopoly" - friendly_name = "monopoly" - location = "US" -} - -resource "google_bigquery_table" "default" { - dataset_id = google_bigquery_dataset.default.dataset_id - table_id = "credit_card_statements" - - external_data_configuration { - source_uris = ["gs://${google_storage_bucket.default.name}/*.csv"] - source_format = "CSV" - autodetect = true - - csv_options { - quote = "\"" - } - - hive_partitioning_options { - mode = "AUTO" - source_uri_prefix = "gs://${google_storage_bucket.default.name}/" - } - } -} diff --git a/terraform/data.tf b/terraform/data.tf deleted file mode 100644 index b4c97815..00000000 --- a/terraform/data.tf +++ /dev/null @@ -1,4 +0,0 @@ -data "google_secret_manager_secret_version" "default" { - secret = "monopoly-gmail-token" - project = var.project_id -} diff --git a/terraform/local.tf b/terraform/local.tf deleted file mode 100644 index 3c66ba43..00000000 --- a/terraform/local.tf +++ /dev/null @@ -1,4 +0,0 @@ -locals { - container_uri = "${var.region}-docker.pkg.dev/${var.project_id}/monopoly/monopoly:main" - cloud_run_scheduler_prefix = "https://${var.region}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${var.project_id}" -} diff --git a/terraform/main.tf b/terraform/main.tf deleted file mode 100644 index 91fc218b..00000000 --- a/terraform/main.tf +++ /dev/null @@ -1,109 +0,0 @@ -resource "google_service_account" "default" { - account_id = "monopoly" - display_name = "Monopoly" -} - -resource "random_id" "bucket_prefix" { - byte_length = 8 -} - -resource "google_storage_bucket" "default" { - name = "monopoly-${random_id.bucket_prefix.hex}" - location = "US" - storage_class = "STANDARD" -} - -resource "google_artifact_registry_repository" "default" { - location = var.region - repository_id = "monopoly" - format = "DOCKER" -} - -resource "google_secret_manager_secret_iam_binding" "default" { - secret_id = data.google_secret_manager_secret_version.default.secret - project = data.google_secret_manager_secret_version.default.project - role = "roles/secretmanager.secretAccessor" - - members = [ - "serviceAccount:${google_service_account.default.email}" - ] -} - -resource "google_storage_bucket_iam_member" "default" { - bucket = google_storage_bucket.default.name - role = "roles/storage.admin" - member = "serviceAccount:${google_service_account.default.email}" -} - -resource "google_cloud_run_v2_job" "default" { - name = "monopoly-tf" - location = var.region - - template { - template { - service_account = google_service_account.default.email - max_retries = 1 - - containers { - image = local.container_uri - - env { - name = "PUBSUB_TOPIC" - value = google_pubsub_topic.default.id - } - env { - name = "GMAIL_ADDRESS" - value = var.gmail_address - } - env { - name = "PROJECT_ID" - value = var.project_id - } - env { - name = "GCS_BUCKET" - value = google_storage_bucket.default.name - } - env { - name = "SECRET_ID" - value = var.gmail_credential_secret - } - env { - name = "TRUSTED_USER_EMAILS" - value = jsonencode(var.trusted_emails) - } - env { - name = "OCBC_PDF_PASSWORD" - value = var.ocbc_password - } - env { - name = "HSBC_PDF_PASSWORD_PREFIX" - value = var.hsbc_password_prefix - } - } - } - } -} - -resource "google_cloud_scheduler_job" "default" { - name = "hourly-bank-statement-extraction" - description = "Extracts bank statements" - schedule = "0 * * * *" - time_zone = "UTC" - - http_target { - http_method = "POST" - uri = "${local.cloud_run_scheduler_prefix}/jobs/${google_cloud_run_v2_job.default.name}:run" - oauth_token { - service_account_email = google_service_account.default.email - } - } -} - -resource "google_project_iam_binding" "cloud_run_invoker" { - project = var.project_id - role = "roles/run.invoker" - - members = [ - "serviceAccount:${google_service_account.default.email}" - ] -} diff --git a/terraform/providers.tf b/terraform/providers.tf deleted file mode 100644 index 75588d90..00000000 --- a/terraform/providers.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_providers { - google = { - source = "hashicorp/google" - version = "4.75.1" - } - } -} - -provider "google" { - project = var.project_id - region = var.region -} diff --git a/terraform/pubsub.tf b/terraform/pubsub.tf deleted file mode 100644 index 588178d4..00000000 --- a/terraform/pubsub.tf +++ /dev/null @@ -1,19 +0,0 @@ -resource "google_pubsub_topic" "default" { - name = "monopoly-topic" - project = var.project_id - - message_retention_duration = "86600s" -} - -module "pubsub_topic-iam-bindings" { - source = "terraform-google-modules/iam/google//modules/pubsub_topics_iam" - project = var.project_id - pubsub_topics = [google_pubsub_topic.default.id] - mode = "authoritative" - - bindings = { - "roles/pubsub.publisher" = [ - "serviceAccount:gmail-api-push@system.gserviceaccount.com", - ] - } -} diff --git a/terraform/variables.tf b/terraform/variables.tf deleted file mode 100644 index 291f15bd..00000000 --- a/terraform/variables.tf +++ /dev/null @@ -1,38 +0,0 @@ -variable "ocbc_password" { - description = "Password for encrypted OCBC PDFs" - type = string - sensitive = true -} - -variable "hsbc_password_prefix" { - description = "Prefix of password for encrypted HSBC PDFs" - type = string - sensitive = true -} - -variable "gmail_address" { - description = "Gmail address to which bank statements/transactions are sent" - type = string - sensitive = true -} - -variable "trusted_emails" { - description = "Trusted user emails" - type = list(any) - sensitive = true -} - -variable "project_id" { - description = "Google project ID" - type = string -} - -variable "region" { - description = "Google region" - type = string -} - -variable "gmail_credential_secret" { - description = "Name of secret containing client secret and token for Gmail account" - type = string -} diff --git a/tests/conftest.py b/tests/conftest.py index 6cef1afe..dc05bacd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ from monopoly.banks import Citibank, Dbs, Hsbc, Ocbc, StandardChartered from monopoly.config import StatementConfig from monopoly.constants import AccountType, BankNames -from monopoly.gmail import Message, MessageAttachment from monopoly.pdf import PdfPage, PdfParser from monopoly.processor import StatementProcessor from monopoly.statement import Statement @@ -57,16 +56,6 @@ def processor(statement_config): yield processor -@pytest.fixture(scope="session") -def message(): - return Message(data={}, gmail_service=mock.Mock()) - - -@pytest.fixture(scope="session") -def attachment(): - return MessageAttachment(filename="test.pdf", file_byte_string=b"Test data") - - @pytest.fixture(scope="function") def parser(): parser = PdfParser(file_path=None) diff --git a/tests/integration/test_main_entrypoint.py b/tests/integration/test_main_entrypoint.py deleted file mode 100644 index d303ae7b..00000000 --- a/tests/integration/test_main_entrypoint.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Test for entrypoint used by Cloud Run""" -from unittest.mock import Mock, PropertyMock, patch - -import pytest - -from monopoly.constants import EmailSubjectRegex -from monopoly.main import process_bank_statement - - -def run_bank_statement_test(message, pattern, subject, expected_result): - with patch( - "monopoly.main.Message.subject", new_callable=PropertyMock - ) as mock_subject: - mock_subject.return_value = subject - bank_class = Mock() - banks = {pattern: bank_class} - - process_bank_statement(message, banks) - - if expected_result == "call": - bank_class.assert_called_once() - - else: - bank_class.assert_not_called() - - -@pytest.mark.parametrize( - "subject,pattern,expected_result", - [ - ("OCBC Bank: Your Credit Card e-Statement", EmailSubjectRegex.OCBC, "call"), - ("Your HSBC VISA REVOLUTION eStatement", EmailSubjectRegex.HSBC, "call"), - ("Random Subject", EmailSubjectRegex.OCBC, "ignore"), - ], -) -def test_process_bank_statements( - monkeypatch, - message, - pattern, - attachment, - subject, - expected_result, -): - monkeypatch.setattr(message, "get_attachment", lambda: attachment) - - run_bank_statement_test(message, pattern, subject, expected_result) diff --git a/tests/unit/test_check_trusted_user.py b/tests/unit/test_check_trusted_user.py deleted file mode 100644 index f332fd2a..00000000 --- a/tests/unit/test_check_trusted_user.py +++ /dev/null @@ -1,27 +0,0 @@ -from monopoly.gmail import Message - -trusted_user_emails = ["trusted_user@gmail.com"] - - -def test_trusted_user(message: Message): - trusted_message = { - "headers": [ - {"name": "From", "value": "Mr Miyagi "}, - ] - } - message.payload = trusted_message - message.trusted_user_emails = trusted_user_emails - - assert message.from_trusted_user - - -def test_untrusted_user(message: Message): - untrusted_message = { - "headers": [ - {"name": "From", "value": "Joe "}, - ] - } - message.payload = untrusted_message - message.trusted_user_emails = trusted_user_emails - - assert not message.from_trusted_user diff --git a/tests/unit/test_generate_name.py b/tests/unit/test_generate_name.py deleted file mode 100644 index 5561f809..00000000 --- a/tests/unit/test_generate_name.py +++ /dev/null @@ -1,36 +0,0 @@ -import uuid -from datetime import datetime -from unittest import mock - -from monopoly.processor import StatementProcessor -from monopoly.storage import generate_name - - -def test_generate_blob_name(processor: StatementProcessor): - with mock.patch.object( - uuid.UUID, "hex", new_callable=mock.PropertyMock - ) as mock_uuid: - mock_uuid.return_value = "12345foo" - - statement_date = datetime(2023, 8, 1) - expected_blob_name = ( - "bank_name=ocbc/" - "account_type=credit/" - "year=2023/" - "month=8/" - "ocbc-credit-2023-08-12345foo.csv" - ) - - actual_blob_name = generate_name( - "blob", processor.statement_config, statement_date - ) - assert expected_blob_name == actual_blob_name - - -def test_generate_file_name(processor: StatementProcessor): - statement_date = datetime(2023, 8, 1) - expected_file_name = "ocbc-credit-2023-08.csv" - - actual_file_name = generate_name("file", processor.statement_config, statement_date) - - assert expected_file_name == actual_file_name diff --git a/tests/unit/test_get_attachment.py b/tests/unit/test_get_attachment.py deleted file mode 100644 index 1bf2fadb..00000000 --- a/tests/unit/test_get_attachment.py +++ /dev/null @@ -1,71 +0,0 @@ -from monopoly.gmail import Message - - -def test_message_with_parts(message: Message): - data = { - "payload": { - "parts": [ - { - "partId": "1", - "filename": "file1.txt", - "body": {"attachmentId": "123"}, - }, - {"partId": "2", "body": {"attachmentId": "456"}}, - ] - } - } - message.payload = data["payload"] - parts = message.parts - - assert len(parts) == 2 - - part1, part2 = parts - assert part1.part_id == "1" - assert part1.filename == "file1.txt" - assert part1.attachment_id == "123" - - assert part2.part_id == "2" - assert part2.filename is None - assert part2.attachment_id == "456" - - -def test_message_with_nested_parts(message: Message): - data = { - "payload": { - "parts": [ - { - "partId": "1", - "filename": "file1.txt", - "body": {"attachmentId": "123"}, - }, - { - "partId": "2", - "body": {"attachmentId": "456"}, - "parts": [ - { - "partId": "3", - "filename": "file2.txt", - "body": {"attachmentId": "789"}, - } - ], - }, - ] - } - } - message.payload = data["payload"] - parts = message.parts - - assert len(parts) == 3 - - part1, part2, nested_part = parts - assert part1.part_id == "1" - assert part1.filename == "file1.txt" - assert part1.attachment_id == "123" - - assert part2.part_id == "2" - assert part2.filename is None - assert part2.attachment_id == "456" - - assert nested_part.part_id == "3" - assert nested_part.filename == "file2.txt" - assert nested_part.attachment_id == "789" diff --git a/tests/unit/test_persist_attachment.py b/tests/unit/test_persist_attachment.py deleted file mode 100644 index 89ebe948..00000000 --- a/tests/unit/test_persist_attachment.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from monopoly.gmail import Message, MessageAttachment - - -def test_temporary_file_create_and_cleanup( - message: Message, attachment: MessageAttachment -): - """ - Test that file exists during context manager, and is - cleaned up after the context manager closes - """ - with message.save(attachment) as file_path: - assert os.path.exists(file_path) - - assert not os.path.exists(file_path) - - -def test_save_writes_correct_data(message: Message, attachment: MessageAttachment): - """ - Test that correct data is written to file - """ - with message.save(attachment) as file_path: - with open(file_path, "rb") as file: - file_contents = file.read() - assert file_contents == attachment.file_byte_string - - -def test_save_exception_cleanup(message: Message, attachment: MessageAttachment): - """ - Test that file is deleted if an exception occurs - """ - try: - with message.save(attachment) as file_path: - assert os.path.exists(file_path) - raise Exception("Simulating an error") - except Exception: - pass - - assert not os.path.exists(file_path)