From 639c56f2030aa350eacba02c16417e835c2e7228 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Fri, 3 Nov 2023 09:33:28 +0100 Subject: [PATCH 01/10] feat: add support for GCS --- README.md | 19 +++++++++++++++++++ dbt_loom/__init__.py | 22 +++++++++++++++++++++- dbt_loom/clients/gcs.py | 27 +++++++++++++++++++++++++++ pyproject.toml | 1 + 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 dbt_loom/clients/gcs.py diff --git a/README.md b/README.md index 0df3345..2e64c4d 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,25 @@ manifests: # which to fetch artifacts. Defaults to the last step. ``` +### Using GCS as an artifact source + +You can use dbt-loom to fetch manifest files from Google Cloud Storage by setting up a `gcs` manifest in your `dbt-loom` config. + +```yaml +manifests: + - name: project_name + type: gcs + config: + bucket_name: + # The name of the bucket where your manifest is stored + + blob_name: + # The blob name of your manifest file + + credentials: + # The OAuth2 Credentials to use. If not passed, falls back to the default inferred from the environment. +``` + ## How does it work? As of dbt-core 1.6.0-b8, there now exists a `dbtPlugin` class which defines functions that can diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 4b279ca..08fe91b 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -13,6 +13,7 @@ from pydantic import BaseModel from .clients.dbt_cloud import DbtCloud +from .clients.gcs import GCSClient class ManifestReferenceType(str, Enum): @@ -20,6 +21,7 @@ class ManifestReferenceType(str, Enum): file = "file" dbt_cloud = "dbt_cloud" + gcs = "gcs" class FileReferenceConfig(BaseModel): @@ -36,13 +38,19 @@ class DbtCloudReferenceConfig(BaseModel): api_endpoint: Optional[str] = None step: Optional[int] = None +class GCSReferenceConfig(BaseModel): + """Configuration for a GCS reference""" + + bucket_name: str + blob_name: str + credentials: Optional[str] = None class ManifestReference(BaseModel): """Reference information for a manifest to be loaded into dbt-loom.""" name: str type: ManifestReferenceType - config: Union[FileReferenceConfig, DbtCloudReferenceConfig] + config: Union[FileReferenceConfig, DbtCloudReferenceConfig, GCSReferenceConfig] class dbtLoomConfig(BaseModel): @@ -60,6 +68,7 @@ def __init__(self): self.loading_functions = { ManifestReferenceType.file: self.load_from_local_filesystem, ManifestReferenceType.dbt_cloud: self.load_from_dbt_cloud, + ManifestReferenceType.gcs: self.load_from_gcs, } @staticmethod @@ -79,6 +88,17 @@ def load_from_dbt_cloud(config: DbtCloudReferenceConfig) -> Dict: return client.get_models(config.job_id, step=config.step) + @staticmethod + def load_from_gcs(config: GCSReferenceConfig) -> Dict: + """Load a manifest dictionary from a GCS bucket.""" + gcs_client = GCSClient( + bucket_name=config.bucket_name, + blob_name=config.blob_name, + credentials=config.credentials + ) + + return gcs_client.load_manifest() + def load(self, manifest_reference: ManifestReference) -> Dict: """Load a manifest dictionary based on a ManifestReference input.""" diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py new file mode 100644 index 0000000..0107cf7 --- /dev/null +++ b/dbt_loom/clients/gcs.py @@ -0,0 +1,27 @@ +from typing import Dict, Optional +import json + +from google.cloud import storage + +class GCSClient: + """Client for GCS. Fetches manifest for a given bucket.""" + + def __init__( + self, + bucket_name: str, + blob_name: str, + credentials: Optional[str] = None, + ) -> None: + self.bucket_name = bucket_name + self.blob_name = blob_name + self.credentials = credentials + + def load_manifest(self) -> Dict: + """Load a manifest json from a GCS bucket.""" + client = storage.Client.from_service_account_json(self.credentials) if self.credentials else storage.Client() + bucket = client.get_bucket(self.bucket_name) + blob = bucket.get_blob(self.blob_name) + if not blob: + raise Exception(f"The blob `{self.blob_name}` does not exist in bucket `{self.bucket_name}`.") + manifest_json = blob.download_as_text() + return json.loads(manifest_json) diff --git a/pyproject.toml b/pyproject.toml index 26cdc61..42d3215 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ version_files = ["pyproject.toml:^version"] python = ">=3.9,<3.12" dbt-core = "1.6.0" requests = "^2.31.0" +google-cloud-storage = "2.13.0" [tool.poetry.group.dev.dependencies] ruff = "^0.0.278" From 231d6ce83344c881f366ba2f585168d64093dfa1 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Sat, 4 Nov 2023 16:04:16 +0100 Subject: [PATCH 02/10] feat: add error handling when blob is not a valid json --- dbt_loom/clients/gcs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py index 0107cf7..34ce564 100644 --- a/dbt_loom/clients/gcs.py +++ b/dbt_loom/clients/gcs.py @@ -23,5 +23,10 @@ def load_manifest(self) -> Dict: blob = bucket.get_blob(self.blob_name) if not blob: raise Exception(f"The blob `{self.blob_name}` does not exist in bucket `{self.bucket_name}`.") + manifest_json = blob.download_as_text() - return json.loads(manifest_json) + + try: + return json.loads(manifest_json) + except: + raise Exception(f"The blob `{self.blob_name}` is not a valid JSON.") From e3e70a7725a2e4e7a4d49cb86e0c048f3599f7f8 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Sat, 4 Nov 2023 16:23:56 +0100 Subject: [PATCH 03/10] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2e64c4d..7d98b23 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ dbt-loom currently supports obtaining model definitions from: - Local manifest files - dbt Cloud +- GCS - S3-compatible object storage services [TODO] :warning: **dbt Core's plugin functionality is still in beta. Please note that this may break in the future as dbt Labs solidifies the dbt plugin API in future versions.** From 87e2202e8e1b470c58c8de1008841cd7e6a7b2f3 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Mon, 6 Nov 2023 21:04:40 +0100 Subject: [PATCH 04/10] fix: types and terminology --- README.md | 6 +- dbt_loom/__init__.py | 6 +- dbt_loom/clients/gcs.py | 13 +- poetry.lock | 274 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 285 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7d98b23..9719f80 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,10 @@ manifests: bucket_name: # The name of the bucket where your manifest is stored - blob_name: - # The blob name of your manifest file + object_name: + # The object name of your manifest file - credentials: + credentials: # The OAuth2 Credentials to use. If not passed, falls back to the default inferred from the environment. ``` diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 08fe91b..2f063e2 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -42,8 +42,8 @@ class GCSReferenceConfig(BaseModel): """Configuration for a GCS reference""" bucket_name: str - blob_name: str - credentials: Optional[str] = None + object_name: str + credentials: Optional[Path] = None class ManifestReference(BaseModel): """Reference information for a manifest to be loaded into dbt-loom.""" @@ -93,7 +93,7 @@ def load_from_gcs(config: GCSReferenceConfig) -> Dict: """Load a manifest dictionary from a GCS bucket.""" gcs_client = GCSClient( bucket_name=config.bucket_name, - blob_name=config.blob_name, + object_name=config.object_name, credentials=config.credentials ) diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py index 34ce564..9dff37c 100644 --- a/dbt_loom/clients/gcs.py +++ b/dbt_loom/clients/gcs.py @@ -1,5 +1,6 @@ from typing import Dict, Optional import json +from pathlib import Path from google.cloud import storage @@ -9,24 +10,24 @@ class GCSClient: def __init__( self, bucket_name: str, - blob_name: str, - credentials: Optional[str] = None, + object_name: str, + credentials: Optional[Path] = None, ) -> None: self.bucket_name = bucket_name - self.blob_name = blob_name + self.object_name = object_name self.credentials = credentials def load_manifest(self) -> Dict: """Load a manifest json from a GCS bucket.""" client = storage.Client.from_service_account_json(self.credentials) if self.credentials else storage.Client() bucket = client.get_bucket(self.bucket_name) - blob = bucket.get_blob(self.blob_name) + blob = bucket.get_blob(self.object_name) if not blob: - raise Exception(f"The blob `{self.blob_name}` does not exist in bucket `{self.bucket_name}`.") + raise Exception(f"The object `{self.object_name}` does not exist in bucket `{self.bucket_name}`.") manifest_json = blob.download_as_text() try: return json.loads(manifest_json) except: - raise Exception(f"The blob `{self.blob_name}` is not a valid JSON.") + raise Exception(f"The object `{self.object_name}` is not a valid JSON.") diff --git a/poetry.lock b/poetry.lock index 94ad857..c211bf2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "agate" @@ -98,6 +98,17 @@ 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.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + [[package]] name = "certifi" version = "2023.7.22" @@ -470,6 +481,206 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "google-api-core" +version = "2.12.0" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.12.0.tar.gz", hash = "sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553"}, + {file = "google_api_core-2.12.0-py3-none-any.whl", hash = "sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +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-auth" +version = "2.23.4" +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"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[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-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-storage" +version = "2.13.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.13.0.tar.gz", hash = "sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7"}, + {file = "google_cloud_storage-2.13.0-py2.py3-none-any.whl", hash = "sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +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.61.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"}, +] + +[package.dependencies] +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 = "hologram" version = "0.0.16" @@ -665,6 +876,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -929,6 +1150,31 @@ files = [ {file = "protobuf-4.24.1.tar.gz", hash = "sha256:44837a5ed9c9418ad5d502f89f28ba102e9cd172b6668bc813f21716f9273348"}, ] +[[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 = "pycparser" version = "2.21" @@ -1115,6 +1361,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1122,8 +1369,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1140,6 +1394,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1147,6 +1402,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1173,6 +1429,20 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[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 = "ruff" version = "0.0.278" @@ -1309,4 +1579,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "1c986c92f65c1c882fb91eddffd8dff29609b71751e5f52f0340f4fec57711b7" +content-hash = "8ece81261a3ad08fce5023b186833f7cb96be0b63b26ca74dcf6f0887594da76" From 91231d6be856d8a3f9a6f39e5e964aa6537ef708 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Mon, 6 Nov 2023 21:15:21 +0100 Subject: [PATCH 05/10] fix: apply pre-commit checks --- README.md | 8 ++++---- dbt_loom/__init__.py | 9 +++++---- dbt_loom/clients/gcs.py | 19 +++++++++++++------ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9719f80..9d7b42b 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ manifests: config: account_id: - # Job ID pertains to the job that you'd like to fetch artifacts from + # Job ID pertains to the job that you'd like to fetch artifacts from. job_id: api_endpoint: @@ -95,10 +95,10 @@ manifests: type: gcs config: bucket_name: - # The name of the bucket where your manifest is stored - + # The name of the bucket where your manifest is stored. + object_name: - # The object name of your manifest file + # The object name of your manifest file. credentials: # The OAuth2 Credentials to use. If not passed, falls back to the default inferred from the environment. diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 2f063e2..93d3aa8 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union - import yaml from dbt.contracts.graph.node_args import ModelNodeArgs from dbt.plugins.manager import dbt_hook, dbtPlugin @@ -38,13 +37,15 @@ class DbtCloudReferenceConfig(BaseModel): api_endpoint: Optional[str] = None step: Optional[int] = None + class GCSReferenceConfig(BaseModel): """Configuration for a GCS reference""" - + bucket_name: str object_name: str credentials: Optional[Path] = None + class ManifestReference(BaseModel): """Reference information for a manifest to be loaded into dbt-loom.""" @@ -94,9 +95,9 @@ def load_from_gcs(config: GCSReferenceConfig) -> Dict: gcs_client = GCSClient( bucket_name=config.bucket_name, object_name=config.object_name, - credentials=config.credentials + credentials=config.credentials, ) - + return gcs_client.load_manifest() def load(self, manifest_reference: ManifestReference) -> Dict: diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py index 9dff37c..7aa0660 100644 --- a/dbt_loom/clients/gcs.py +++ b/dbt_loom/clients/gcs.py @@ -1,12 +1,13 @@ -from typing import Dict, Optional import json from pathlib import Path +from typing import Dict, Optional from google.cloud import storage + class GCSClient: """Client for GCS. Fetches manifest for a given bucket.""" - + def __init__( self, bucket_name: str, @@ -19,14 +20,20 @@ def __init__( def load_manifest(self) -> Dict: """Load a manifest json from a GCS bucket.""" - client = storage.Client.from_service_account_json(self.credentials) if self.credentials else storage.Client() + client = ( + storage.Client.from_service_account_json(self.credentials) + if self.credentials + else storage.Client() + ) bucket = client.get_bucket(self.bucket_name) blob = bucket.get_blob(self.object_name) if not blob: - raise Exception(f"The object `{self.object_name}` does not exist in bucket `{self.bucket_name}`.") - + raise Exception( + f"The object `{self.object_name}` does not exist in bucket `{self.bucket_name}`." + ) + manifest_json = blob.download_as_text() - + try: return json.loads(manifest_json) except: From 41559fc3561037b351bab0e8dbf473d2259218ad Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Wed, 8 Nov 2023 10:07:13 +0100 Subject: [PATCH 06/10] fix: remove backticks from identifier --- dbt_loom/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 93d3aa8..83ef3cd 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -150,7 +150,7 @@ def convert_model_nodes_to_model_node_args( unique_id: ModelNodeArgs( name=node.get("name"), package_name=node.get("package_name"), - identifier=node.get("relation_name").split(".")[-1].replace('"', ""), + identifier=node.get("relation_name").split(".")[-1].replace('"', "").replace('`',""), schema=node.get("schema"), database=node.get("database"), relation_name=node.get("relation_name"), From f86275a402d82a97ec200dc0800fca2367cf6169 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Fri, 10 Nov 2023 14:15:07 +0100 Subject: [PATCH 07/10] feat: add project_id in config --- dbt_loom/__init__.py | 2 ++ dbt_loom/clients/gcs.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 83ef3cd..4a895da 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -41,6 +41,7 @@ class DbtCloudReferenceConfig(BaseModel): class GCSReferenceConfig(BaseModel): """Configuration for a GCS reference""" + project_id: str bucket_name: str object_name: str credentials: Optional[Path] = None @@ -93,6 +94,7 @@ def load_from_dbt_cloud(config: DbtCloudReferenceConfig) -> Dict: def load_from_gcs(config: GCSReferenceConfig) -> Dict: """Load a manifest dictionary from a GCS bucket.""" gcs_client = GCSClient( + project_id=config.project_id, bucket_name=config.bucket_name, object_name=config.object_name, credentials=config.credentials, diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py index 7aa0660..a974138 100644 --- a/dbt_loom/clients/gcs.py +++ b/dbt_loom/clients/gcs.py @@ -10,10 +10,12 @@ class GCSClient: def __init__( self, + project_id: str, bucket_name: str, object_name: str, credentials: Optional[Path] = None, ) -> None: + self.project_id = project_id self.bucket_name = bucket_name self.object_name = object_name self.credentials = credentials @@ -21,9 +23,9 @@ def __init__( def load_manifest(self) -> Dict: """Load a manifest json from a GCS bucket.""" client = ( - storage.Client.from_service_account_json(self.credentials) + storage.Client.from_service_account_json(self.credentials, project=self.project_id) if self.credentials - else storage.Client() + else storage.Client(project=self.project_id) ) bucket = client.get_bucket(self.bucket_name) blob = bucket.get_blob(self.object_name) From db6fbbf1462663a298c81fc44485655a7e527344 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Fri, 10 Nov 2023 14:39:07 +0100 Subject: [PATCH 08/10] feat: handle environment variables in config file --- dbt_loom/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 4a895da..633a9fe 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -3,6 +3,7 @@ from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union +import re import yaml from dbt.contracts.graph.node_args import ModelNodeArgs @@ -192,7 +193,18 @@ def read_config(self, path: Path) -> Optional[dbtLoomConfig]: if not path.exists(): return None - return dbtLoomConfig(**yaml.load(open(path), yaml.SafeLoader)) + with open(path) as file: + config_content = file.read() + + config_content = self.replace_env_variables(config_content) + + return dbtLoomConfig(**yaml.load(config_content, yaml.SafeLoader)) + + @staticmethod + def replace_env_variables(config_str: str) -> str: + """Replace environment variable placeholders in the configuration string.""" + pattern = r'\$(\w+)|\$\{([^}]+)\}' + return re.sub(pattern, lambda match: os.environ.get(match.group(1) if match.group(1) is not None else match.group(2), ''), config_str) def initialize(self) -> None: """Initialize the plugin""" From e246863204d079c0ed4c914c67c4fed1eef452e8 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Fri, 10 Nov 2023 15:45:58 +0100 Subject: [PATCH 09/10] fix: apply pre-commit --- dbt_loom/__init__.py | 19 ++++++++++++++----- dbt_loom/clients/gcs.py | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/dbt_loom/__init__.py b/dbt_loom/__init__.py index 633a9fe..9d06437 100644 --- a/dbt_loom/__init__.py +++ b/dbt_loom/__init__.py @@ -1,9 +1,9 @@ import json import os +import re from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union -import re import yaml from dbt.contracts.graph.node_args import ModelNodeArgs @@ -153,7 +153,10 @@ def convert_model_nodes_to_model_node_args( unique_id: ModelNodeArgs( name=node.get("name"), package_name=node.get("package_name"), - identifier=node.get("relation_name").split(".")[-1].replace('"', "").replace('`',""), + identifier=node.get("relation_name") + .split(".")[-1] + .replace('"', "") + .replace('`', ""), schema=node.get("schema"), database=node.get("database"), relation_name=node.get("relation_name"), @@ -195,16 +198,22 @@ def read_config(self, path: Path) -> Optional[dbtLoomConfig]: with open(path) as file: config_content = file.read() - + config_content = self.replace_env_variables(config_content) return dbtLoomConfig(**yaml.load(config_content, yaml.SafeLoader)) - + @staticmethod def replace_env_variables(config_str: str) -> str: """Replace environment variable placeholders in the configuration string.""" pattern = r'\$(\w+)|\$\{([^}]+)\}' - return re.sub(pattern, lambda match: os.environ.get(match.group(1) if match.group(1) is not None else match.group(2), ''), config_str) + return re.sub( + pattern, + lambda match: os.environ.get( + match.group(1) if match.group(1) is not None else match.group(2), '' + ), + config_str, + ) def initialize(self) -> None: """Initialize the plugin""" diff --git a/dbt_loom/clients/gcs.py b/dbt_loom/clients/gcs.py index a974138..4a9786b 100644 --- a/dbt_loom/clients/gcs.py +++ b/dbt_loom/clients/gcs.py @@ -23,7 +23,9 @@ def __init__( def load_manifest(self) -> Dict: """Load a manifest json from a GCS bucket.""" client = ( - storage.Client.from_service_account_json(self.credentials, project=self.project_id) + storage.Client.from_service_account_json( + self.credentials, project=self.project_id + ) if self.credentials else storage.Client(project=self.project_id) ) From 1fb9543a84c066640e6e523e0a8e11da2decdb56 Mon Sep 17 00:00:00 2001 From: Nawfel Bacha Date: Sun, 12 Nov 2023 00:08:22 +0100 Subject: [PATCH 10/10] docs: update readme (env variables + project_id) --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 9d7b42b..81a7d20 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ manifests: - name: project_name type: gcs config: + project_id: + # The alphanumeric ID of the GCP project that contains your target bucket. + bucket_name: # The name of the bucket where your manifest is stored. @@ -104,6 +107,24 @@ manifests: # The OAuth2 Credentials to use. If not passed, falls back to the default inferred from the environment. ``` +### Using environment variables + +You can easily incorporate your own environment variables into the config file. This allows for dynamic configuration values that can change based on the environment. To specify an environment variable in the `dbt-loom` config file, use one of the following formats: + +`${ENV_VAR}` or `$ENV_VAR` + +#### Example: + +```yaml +manifests: + - name: revenue + type: gcs + config: + project_id: ${GCP_PROJECT} + bucket_name: ${GCP_BUCKET} + object_name: ${MANIFEST_PATH} +``` + ## How does it work? As of dbt-core 1.6.0-b8, there now exists a `dbtPlugin` class which defines functions that can