Repository navigation
Google drive api #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Google drive api #19
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
d86dab1
feat(ci): added google auth api key
raymondcen 7a01cbf
Merge branch 'main' into google-drive-api
raymondcen 35b6868
fix(ci): use run_pipeline instead of calling each file manually
raymondcen a9f509e
fix(ci): now fetches properly from gogole api
raymondcen e17b8a2
feat(ci): added printed statements to look where ci is at with proces…
raymondcen 5ecd89e
feat(ci): more print statement checkpoints
raymondcen 6bdf0f7
fix(ci): unbuffer python print statemets
raymondcen 087f0ef
Revert "fix(ci): unbuffer python print statemets"
raymondcen fff13b6
fix(ci): unbuffer python print statemets
raymondcen 7cfd487
fix(ci): lowered amount of papers used
raymondcen 0219e84
forat(black): reformatted code
raymondcen d482a60
feat(model pipeline): modified model pipeline to include api mode
raymondcen acd03b6
Revert "feat(model pipeline): modified model pipeline to include api …
raymondcen 9003763
feat(model pipeline): modified model pipeline to include api mode
raymondcen 3fc2da9
feat(model pipeline): included api mode in full pipeline but local mo…
raymondcen 43ce8b7
docs: updated contributing.md to match new pipeline
raymondcen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| """CI pipeline: stream a small, fixed sample from Google Drive and run extraction. | ||
|
|
||
| Requirements: | ||
| - Env GOOGLE_SERVICE_ACCOUNT_JSON set to Service Account JSON string. | ||
| - Env GOOGLE_DRIVE_ROOT_FOLDER_ID set to the Drive folder containing 'useful' and 'not-useful'. | ||
| - Optional env CI_FILES_PER_CLASS (default 1). | ||
|
|
||
| This script DOES NOT save PDFs locally. It streams bytes and writes extracted text | ||
| into data/processed-text/*.txt, and writes data/labels.json. No training. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import json | ||
| from pathlib import Path | ||
| from typing import Dict, List | ||
| import subprocess | ||
|
|
||
| import sys | ||
| from pathlib import Path as _Path | ||
|
|
||
| sys.path.append(str(_Path(__file__).resolve().parents[1])) # add repo root to sys.path | ||
|
|
||
| from scripts.env_loader import load_env | ||
|
|
||
| load_env() # Load .env file if present (for local dev) | ||
|
|
||
| from scripts.google_drive.drive_io import ( | ||
| get_drive_service, | ||
| find_child_folder_id, | ||
| list_pdfs_in_folder, | ||
| download_file_bytes, | ||
| sanitize_filename, | ||
| ) | ||
| from src.preprocessing.pdf_text_extraction import extract_text_from_pdf_bytes | ||
|
|
||
|
|
||
| def write_labels(labels: Dict[str, str], output_file: Path): | ||
| output_file.parent.mkdir(parents=True, exist_ok=True) | ||
| with output_file.open("w", encoding="utf-8") as f: | ||
| json.dump(labels, f, indent=2) | ||
|
|
||
|
|
||
| def main(): | ||
| root_id = os.environ.get("GOOGLE_DRIVE_ROOT_FOLDER_ID") | ||
| if not root_id: | ||
| raise RuntimeError("Missing GOOGLE_DRIVE_ROOT_FOLDER_ID environment variable") | ||
|
|
||
| per_class = 5 # Fixed set size for CI | ||
|
|
||
| print("Connecting to Google Drive...") | ||
| service = get_drive_service() | ||
| print("Connected to Google Drive") | ||
|
|
||
| print(f"Looking for 'useful' and 'not-useful' folders under root {root_id}...") | ||
| useful_id = find_child_folder_id(service, root_id, "useful") | ||
| not_useful_id = find_child_folder_id(service, root_id, "not-useful") | ||
| if not useful_id: | ||
| raise RuntimeError(f"Could not find 'useful' subfolder under root folder {root_id}") | ||
| if not not_useful_id: | ||
| raise RuntimeError(f"Could not find 'not-useful' subfolder under root folder {root_id}") | ||
| print(f"Found both folders (useful: {useful_id}, not-useful: {not_useful_id})") | ||
|
|
||
| # Prepare output directory | ||
| out_dir = Path("data/processed-text") | ||
| out_dir.mkdir(parents=True, exist_ok=True) | ||
| print(f"Output directory ready: {out_dir}") | ||
|
|
||
| labels: Dict[str, str] = {} | ||
|
|
||
| for folder_id, label in [(useful_id, "useful"), (not_useful_id, "not useful")]: | ||
| print(f"\nProcessing '{label}' folder (max {per_class} PDFs)...") | ||
| files = list_pdfs_in_folder(service, folder_id, max_files=per_class) | ||
| print(f"Found {len(files)} PDFs in '{label}' folder") | ||
| for idx, f in enumerate(files, 1): | ||
| pdf_name = f.get("name", f.get("id", "file")) | ||
| print(f"[{idx}/{len(files)}] Processing: {pdf_name}") | ||
| pdf_bytes = download_file_bytes(service, f["id"]) | ||
| print(f"Downloaded {len(pdf_bytes)} bytes") | ||
| text = extract_text_from_pdf_bytes(pdf_bytes) | ||
| stem = sanitize_filename(pdf_name) | ||
| txt_name = f"{stem}.txt" | ||
| (out_dir / txt_name).write_text(text, encoding="utf-8") | ||
| labels[txt_name] = label | ||
| print(f"Extracted {len(text)} chars to {txt_name}") | ||
|
|
||
| write_labels(labels, Path("data/labels.json")) | ||
| print(f"\nWrote {len(labels)} labels to data/labels.json") | ||
| print(f"Extracted {len(labels)} text files to {out_dir}") | ||
|
|
||
| # Train model on the CI sample | ||
| print("\nStarting model training on CI sample...") | ||
| r = subprocess.run([sys.executable, "src/model/train_model.py"], env={**os.environ, "CI_TRAIN": "1"}) | ||
| if r.returncode != 0: | ||
| print("Model training failed") | ||
| raise SystemExit(r.returncode) | ||
| print("Model training complete") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| """Load environment variables from .env file for local development.""" | ||
|
|
||
| from pathlib import Path | ||
|
|
||
|
|
||
| def load_env(): | ||
| """Load .env file from repo root if it exists.""" | ||
| env_file = Path(__file__).resolve().parents[1] / ".env" | ||
| if not env_file.exists(): | ||
| return # No .env file, assume env vars set manually or in CI | ||
|
|
||
| import os | ||
|
|
||
| with env_file.open("r", encoding="utf-8") as f: | ||
| for line in f: | ||
| line = line.strip() | ||
| if not line or line.startswith("#"): | ||
| continue | ||
| if "=" in line: | ||
| key, value = line.split("=", 1) | ||
| os.environ.setdefault(key.strip(), value.strip()) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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))