Skip to content

Commit 152de50

Browse files
google drive factory update
1 parent 03a0a5b commit 152de50

3 files changed

Lines changed: 99 additions & 27 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ MOTHERDUCK_TOKEN=
44
MOTHERDUCK_DATABASE=
55
MOTHERDUCK_PROD_SCHEMA=
66
GOOGLE_DRIVE_FOLDER_ID=
7-
GOOGLE_DRIVE_CREDENTIALS_PATH=
7+
GOOGLE_SERVICE_ACCOUNT_JSON=
88
ENVIRONMENT=
99
DBT_TARGET=
1010
PYTHONLEGACYWINDOWSSTDIO=

econ_data_platform/econ_data_platform/assets/ingestion/realtor.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import dagster as dg
22
from econ_data_platform.resources.motherduck import MotherDuckResource
3+
from econ_data_platform.resources.google_drive import (
4+
GoogleDriveResource,
5+
google_drive_resource,
6+
)
37

48
import polars as pl
59
from io import StringIO
6-
from google.oauth2 import service_account
7-
from googleapiclient.discovery import build
10+
811
from dataclasses import dataclass
9-
from collections.abc import Sequence
1012

1113
from datetime import datetime
1214
import os
@@ -20,7 +22,9 @@ class DriveFile:
2022
modifiedTime: str
2123

2224

23-
def realtor_asset_factory(file_definition: DriveFile) -> dg.Definitions:
25+
def realtor_asset_factory(
26+
file_definition: DriveFile, google_drive: GoogleDriveResource
27+
) -> dg.Definitions:
2428
file_name, _ = os.path.splitext(file_definition.name)
2529
file_id = file_definition.id
2630

@@ -30,13 +34,14 @@ def realtor_asset_factory(file_definition: DriveFile) -> dg.Definitions:
3034
kinds={"polars", "duckdb", "google_drive"},
3135
)
3236
def read_csv_from_drive(
33-
context: dg.AssetExecutionContext, md: MotherDuckResource
37+
context: dg.AssetExecutionContext,
38+
md: MotherDuckResource,
39+
google_drive: GoogleDriveResource,
3440
) -> dg.MaterializeResult:
3541
"""Read CSV directly from Google Drive file ID into a polars DataFrame"""
3642
context.log.info(f"Reading file {file_name} from Google Drive")
37-
request = service.files().get_media(fileId=file_id)
38-
content = request.execute()
39-
csv_string = content.decode("utf-8")
43+
request = google_drive.request_content(file_id)
44+
csv_string = request.decode("utf-8")
4045
df = pl.read_csv(StringIO(csv_string))
4146
md.drop_create_duck_db_table(file_name, df)
4247

@@ -57,14 +62,12 @@ def read_csv_from_drive(
5762
job_name=f"{file_name}_job",
5863
minimum_interval_seconds=15,
5964
)
60-
def file_sensor(context):
65+
def file_sensor(context, google_drive: GoogleDriveResource):
6166
# Get current modification time from cursor
6267
last_mtime = float(context.cursor) if context.cursor else 0
6368

6469
# Get file details from Drive
65-
file_metadata = (
66-
service.files().get(fileId=file_id, fields="modifiedTime").execute()
67-
)
70+
file_metadata = google_drive.get_file_metadata(file_id)
6871
context.log.info(f"File metadata: {file_metadata}")
6972

7073
current_mtime = datetime.strptime(
@@ -83,24 +86,16 @@ def file_sensor(context):
8386
assets=[read_csv_from_drive],
8487
jobs=[file_job],
8588
sensors=[file_sensor],
89+
resources={"google_drive": google_drive},
8690
)
8791

8892

89-
SCOPES = ["https://www.googleapis.com/auth/drive.readonly"]
90-
credentials = service_account.Credentials.from_service_account_file(
91-
"creds.json", scopes=SCOPES
92-
)
93-
service = build("drive", "v3", credentials=credentials)
93+
# Fetch files from the Google Drive folder using properly initialized _client
9494
folder_id = os.environ.get("GOOGLE_DRIVE_FOLDER_ID", "")
95+
file_results = google_drive_resource.retrieve_files(folder_id).get("files", [])
9596

96-
# Get files from folder
97-
query = f"'{folder_id}' in parents and mimeType='text/csv'"
98-
results = (
99-
service.files()
100-
.list(q=query, fields="files(id, name, createdTime, modifiedTime)")
101-
.execute()
102-
)
103-
97+
# Create realtor definitions dynamically
10498
realtor_definitions = [
105-
realtor_asset_factory(DriveFile(**file)) for file in results.get("files", [])
99+
realtor_asset_factory(DriveFile(**file), google_drive_resource)
100+
for file in file_results
106101
]
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import json
2+
import os
3+
from datetime import datetime
4+
from io import StringIO
5+
6+
import dagster as dg
7+
import polars as pl
8+
from google.oauth2 import service_account
9+
from googleapiclient.discovery import build
10+
from pydantic import BaseModel, PrivateAttr
11+
12+
13+
class DriveFile(BaseModel):
14+
id: str
15+
name: str
16+
createdTime: str
17+
modifiedTime: str
18+
19+
20+
class GoogleDriveClient:
21+
"""Handles the Google Drive client creation and API interactions."""
22+
23+
def __init__(self, credentials_json: dict):
24+
self.credentials = service_account.Credentials.from_service_account_info(
25+
credentials_json, scopes=["https://www.googleapis.com/auth/drive.readonly"]
26+
)
27+
self.service = build("drive", "v3", credentials=self.credentials)
28+
29+
def retrieve_files(self, folder_id: str):
30+
"""Query for files in a Google Drive folder."""
31+
query = f"'{folder_id}' in parents and mimeType='text/csv'"
32+
return (
33+
self.service.files()
34+
.list(q=query, fields="files(id, name, createdTime, modifiedTime)")
35+
.execute()
36+
)
37+
38+
def request_content(self, file_id: str):
39+
"""Fetch file content from Google Drive using file_id."""
40+
request = self.service.files().get_media(fileId=file_id)
41+
return request.execute()
42+
43+
def get_file_metadata(self, file_id: str, fields: str = "modifiedTime"):
44+
"""Get metadata for a specific file."""
45+
return self.service.files().get(fileId=file_id, fields=fields).execute()
46+
47+
48+
class GoogleDriveResource(dg.ConfigurableResource):
49+
"""Resource configuration for Google Drive credentials."""
50+
51+
json_data: str
52+
53+
_client: GoogleDriveClient = PrivateAttr()
54+
55+
def setup_for_execution(self, context: dg.InitResourceContext):
56+
"""Initialize the Google Drive client using the credentials."""
57+
credentials_json = json.loads(self.json_data)
58+
self._client = GoogleDriveClient(credentials_json)
59+
60+
def retrieve_files(self, folder_id: str):
61+
"""Delegates to the client to retrieve files."""
62+
return self._client.retrieve_files(folder_id)
63+
64+
def request_content(self, file_id: str):
65+
"""Delegates to the client to fetch content."""
66+
return self._client.request_content(file_id)
67+
68+
def get_file_metadata(self, file_id: str, fields: str = "modifiedTime"):
69+
"""Delegates to the client to get file metadata."""
70+
return self._client.get_file_metadata(file_id, fields)
71+
72+
73+
google_drive_resource = GoogleDriveResource(
74+
json_data=os.environ["GOOGLE_SERVICE_ACCOUNT_JSON"]
75+
)
76+
77+
google_drive_resource.setup_for_execution(dg.build_init_resource_context())

0 commit comments

Comments
 (0)