Skip to content

Google drive api - #19

Merged
raymondcen merged 16 commits into
mainfrom
google-drive-api
Nov 17, 2025
Merged

raymondcen merged 16 commits into
mainfrom
google-drive-api

Conversation

@raymondcen

Copy link
Copy Markdown
Collaborator

This pull request introduces a robust and flexible pipeline for extracting text from PDFs and training a classification model, with support for both Google Drive streaming and local datasets. The changes refactor the CI and full training workflows, add new utility modules for environment variable loading and Google Drive integration, and update documentation for clarity. The CI pipeline now streams a sample from Drive, while the full pipeline script supports both API and local modes. These improvements enable easier scaling, reproducible runs, and better separation between CI and full training jobs.

Pipeline and Workflow Enhancements:

  • Added scripts/ci_pipeline.py to stream a small, fixed sample of PDFs from Google Drive, extract text, generate labels, and train the model in CI. This script uses environment variables for authentication and folder selection, and does not persist PDFs locally.
  • Added scripts/full_pipeline.py to run the complete training pipeline, supporting both API mode (streaming all PDFs from Drive) and local mode (processing locally downloaded PDFs). This script generates labels and trains the model after extraction.
  • Updated .github/workflows/working_sw.yml to integrate the new CI pipeline, enable model artifact caching and uploading, and clarify the separation between CI and full training workflows. Optional steps for local extraction and preprocessing are now commented for clarity.

Google Drive Integration:

  • Added scripts/google_drive/drive_io.py, a utility module for streaming PDF bytes directly from Google Drive using a service account. It supports shared drives and provides functions for listing, downloading, and sanitizing file names.
  • Added a minimal example in scripts/google_drive/download_from_drive.py showing authentication and credential loading from environment variables.

Environment Variable Management:

  • Added scripts/env_loader.py to automatically load environment variables from a .env file for local development, ensuring sensitive information is never hardcoded.

Documentation Updates:

  • Updated documentation/CONTRIBUTING.md to explain how to run the application with either a local dataset or via the API, including required folder structure and environment variable setup.

Code Cleanup:

  • Removed the obsolete scripts/run_pipeline.py in favor of the new, more flexible pipeline scripts.

Minor Fixes:

  • Added an import for os in src/model/train_model.py to support new environment variable usage.

These changes collectively modernize the pipeline, improve maintainability, and make it easier to run and test the model training process in both CI and local environments.

Comment thread scripts/ci_pipeline.py
Comment on lines +49 to +51

per_class = 5 # Fixed set size for CI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value per_class = 5 is currently hardcoded, which limits configurability and might force us to modify the script when CI requirements change or Drive folder grows. Hardcoded operational parameters can be discouraged in production pipelines because they reduce reproducibility and flexibility. GitHub suggests utilizing environment variables as much as possible as opposed to hardcoding, as it allows a little more flexibility when adjusting values across runs: (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments)

It appears that the CI_FILES_PER_CLASS environment variable is defined, but never used within the script. We could potentially utilize this environment variable with a safe default (5) to allow us to change this value without directing changing the code.

Example:

per_class = int(os.environ.get("CI_FILES_PER_CLASS", 5))

Comment on lines +93 to +101
def download_file_bytes(service, file_id: str) -> bytes:
request = service.files().get_media(fileId=file_id, supportsAllDrives=_use_all_drives())
buf = io.BytesIO()
downloader = MediaIoBaseDownload(buf, request, chunksize=1024 * 1024)
done = False
while not done:
status, done = downloader.next_chunk()
# Optionally, could print progress: status.progress()
return buf.getvalue()

@SeanClay10 SeanClay10 Nov 17, 2025 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google Drive API calls (especially MediaIoBaseDownload) can return transient 5xx errors, connection resets, or throttling responses. Currently, the download loop performs no retry logic. Without retry logic, the pipeline can intermittently fail, especially if hundreds of PDFs are being downloaded from the useful and not useful folders. Since the function call grants read access to the dataset, it might be worth it to make the workflow should confirm that the credentials only request the minimum OAuth scopes required (read only).

Google recommends retrying transient Drive API errors with exponential backoff:
Google Drive API Best Practices – Retry Handling
https://developers.google.com/drive/api/guides/handle-errors

Google Cloud General Retry Guidance
https://cloud.google.com/storage/docs/retry-strategy

We should consider wrapping the MediaIoBaseDownload chunk loop in a retry loop (maybe 3–5 attempts with exponential backoff?) so that in the case that there are connection issues, it will have a chance to recover without immediately failing.

Comment thread src/model/train_model.py
Comment on lines +68 to +77
if tiny_context:
# For tiny datasets, attempt a 50/50 split; if that fails, fall back to train-only.
test_size = 0.5
try:
X_train, X_test, y_train, y_test = train_test_split(texts, labels, test_size=0.2, random_state=42, stratify=labels)
X_train, X_test, y_train, y_test = train_test_split(texts, labels, test_size=test_size, random_state=42, stratify=labels)
except ValueError as e:
print(f"[ERROR] train_test_split failed: {e}")
return None
print(f"[WARN] Stratified split failed on tiny dataset: {e} — training on all data without evaluation.")
perform_eval = False
X_train = texts
y_train = labels

@SeanClay10 SeanClay10 Nov 17, 2025 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback behavior for small CI datasets is to attempt a 50/50 split, then fall back to train only mode if stratification fails. However if the dataset is below a certain threshold, it can potentially produce misleading accuracy metrics and it might be worth it to just avoid the evaluation entirely when the dataset is that small. In other words, we should consider explicitly skipping evaluation when CI_TRAIN = 1 or when the dataset has less than 10 PDFs.

The fallback behavior for small datasets attempts a 50/50 stratified split and then switches to train only mode if the split fails. However, evaluating a model on extremely small datasets (<10 samples or highly imbalanced classes) can produce accuracy metrics that are very misleading. Small sample evaluation introduces misleading variance and does not represent the model's true accuracy. The scikit-learn model evaluation guide warns against test sets that are too small (https://scikit-learn.org/stable/modules/cross_validation.html#limitations). This can affect CI test coverage accuracy because model evaluation acts as a validation check. With extremely small datasets, the metrics become nondeterministic and could cause CI to produce unstable test results.

It might be worth it to just avoid the evaluation entirely when the dataset is that small. In other words, we should consider explicitly skipping evaluation when CI_TRAIN = 1 or when the dataset has less than 10 PDFs instead of allowing it to fail.

Example:

if os.environ.get("CI_TRAIN") or len(labels) < 10:
    perform_eval = False

@raymondcen
raymondcen merged commit 0bba4ba into main Nov 17, 2025
2 checks passed
@raymondcen
raymondcen deleted the google-drive-api branch November 17, 2025 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants