-
Notifications
You must be signed in to change notification settings - Fork 4
CLI MVP #284
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
CLI MVP #284
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
94180f4
feat: implement asset upload and association functionality with manif…
jirhiker ca47509
feat: update asset association command to return blob names and enhan…
jirhiker e17ac7b
feat: update asset association process to support 2-column manifest a…
jirhiker d91f4e5
feat: enhance asset association process with idempotent behavior and …
jirhiker debb511
feat: refactor CLI structure and implement asset association function…
jirhiker c768afc
feat: implement CLI for asset management and CSV uploads
jirhiker 7dd1aac
feat: add water level csv feature
chasetmartin 795fcfd
fix: skip water level feature in test suite
chasetmartin 2486b39
Merge pull request #288 from DataIntegrationGroup/cm-water-level-feature
jirhiker 56babad
Merge branch 'refs/heads/staging' into jir-cli-photo
jirhiker db11629
fix: exclude skipped tests from BDD run in CI workflow
jirhiker 9b24715
refactor: update import paths for asset_helper module
jirhiker a027369
refactor: update import paths for asset_helper module
jirhiker 0192765
feat: enhance water levels CSV upload with output format option
jirhiker 2a5e76a
feat: enhance water levels CSV upload with output format option
jirhiker 48494b6
feat: add water level bulk upload models using Pydantic
jirhiker e50643a
feat: rename project to OcotilloAPI and update CLI command references
jirhiker 4bf1d90
feat: update package dependencies in uv.lock file
jirhiker 17d136b
feat: update FastAPI to version 0.124.2 and add annotated-doc package
jirhiker be5afbd
Merge branch 'refs/heads/staging' into jir-cli-photo
jirhiker 0c09f46
feat: update package versions for pg8000, starlette, and urllib3 in u…
jirhiker 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
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,88 @@ | ||
| # =============================================================================== | ||
| # Copyright 2025 ross | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # =============================================================================== | ||
| from dotenv import load_dotenv | ||
|
|
||
| load_dotenv() | ||
|
|
||
| import click | ||
|
|
||
|
|
||
| @click.group() | ||
| def cli(): | ||
| """Command line interface for managing the application.""" | ||
| pass | ||
|
|
||
|
|
||
| @cli.command() | ||
| def initialize_lexicon(): | ||
| from core.initializers import init_lexicon | ||
|
|
||
| init_lexicon() | ||
|
|
||
|
|
||
| @cli.command() | ||
| @click.argument( | ||
| "root_directory", | ||
| type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True), | ||
| ) | ||
| def associate_assets_command(root_directory: str): | ||
| from cli.service_adapter import associate_assets | ||
|
|
||
| associate_assets(root_directory) | ||
|
|
||
|
|
||
| @cli.command() | ||
| @click.argument( | ||
| "file_path", | ||
| type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True), | ||
| ) | ||
| def well_inventory_csv(file_path: str): | ||
| """ | ||
| parse and upload a csv to database | ||
| """ | ||
| # TODO: use the same helper function used by api to parse and upload a WI csv | ||
| from cli.service_adapter import well_inventory_csv | ||
|
|
||
| well_inventory_csv(file_path) | ||
|
|
||
|
|
||
| @cli.command() | ||
| @click.argument( | ||
| "file_path", | ||
| type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True), | ||
| ) | ||
| @click.option( | ||
| "--output", | ||
| "output_format", | ||
| type=click.Choice(["json"], case_sensitive=False), | ||
| default=None, | ||
| help="Optional output format", | ||
| ) | ||
| def water_levels_csv(file_path: str, output_format: str | None): | ||
| """ | ||
| parse and upload a csv | ||
| """ | ||
| # TODO: use the same helper function used by api to parse and upload a WL csv | ||
| from cli.service_adapter import water_levels_csv | ||
|
|
||
| pretty_json = (output_format or "").lower() == "json" | ||
| water_levels_csv(file_path, pretty_json=pretty_json) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| cli() | ||
|
|
||
| # ============= EOF ============================================= |
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,100 @@ | ||
| # =============================================================================== | ||
| # Copyright 2025 ross | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # =============================================================================== | ||
| import csv | ||
| import io | ||
| import mimetypes | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| from fastapi import UploadFile | ||
| from sqlalchemy import select | ||
|
|
||
| from db import Thing, Asset | ||
| from db.engine import session_ctx | ||
| from services.asset_helper import upload_and_associate | ||
| from services.gcs_helper import get_storage_bucket, make_blob_name_and_uri | ||
| from services.water_level_csv import bulk_upload_water_levels | ||
|
|
||
|
|
||
| def well_inventory_csv(source_file: Path | str): | ||
| if isinstance(source_file, str): | ||
| source_file = Path(source_file) | ||
|
|
||
|
|
||
| def water_levels_csv(source_file: Path | str, *, pretty_json: bool = False): | ||
| if isinstance(source_file, str): | ||
| source_file = Path(source_file) | ||
|
|
||
| result = bulk_upload_water_levels(source_file, pretty_json=pretty_json) | ||
| print(result.stdout) | ||
| if result.stderr: | ||
| print(result.stderr, file=sys.stderr) | ||
| return result.exit_code | ||
|
|
||
|
|
||
| def associate_assets(source_directory: Path | str) -> list[str]: | ||
| """ | ||
| given a directory | ||
| and the directory contains a manifest file | ||
| and the manifest file is a 3-column csv (asset_file_name, thing_name aka pointid, asset_type) | ||
| and the directory contains a set of photos | ||
|
|
||
| then when i run the associate photos command | ||
| the app should save the photos to gcs | ||
| and associate each uploaded photo with the corresponding thing | ||
|
|
||
| """ | ||
| if isinstance(source_directory, str): | ||
| source_directory = Path(source_directory) | ||
| m = source_directory / "manifest.txt" | ||
|
|
||
| bucket = get_storage_bucket() | ||
| uris = [] | ||
| with session_ctx() as sess: | ||
| with open(m, "r") as rf: | ||
| reader = csv.DictReader(rf) | ||
| for row in reader: | ||
| # save file to gcs | ||
| path = row["asset_file_name"].strip() | ||
|
|
||
| with open(source_directory / path, "rb") as fp: | ||
| file = UploadFile( | ||
| io.BytesIO(fp.read()), filename=path, size=len(fp.read()) | ||
| ) | ||
|
|
||
| sql = select(Thing).where(Thing.name == row["thing_name"].strip()) | ||
| thing = sess.scalars(sql).one_or_none() | ||
| if thing: | ||
| # get mime_type from file | ||
| mime_type, encoding = mimetypes.guess_type(path) | ||
| blob_name, uri = make_blob_name_and_uri(file) | ||
| sql = select(Asset).where(Asset.uri == uri) | ||
| existing_asset = sess.scalars(sql).one_or_none() | ||
| if existing_asset: | ||
| continue | ||
| uri, blob_name = upload_and_associate( | ||
| sess, file, bucket, thing, path, **{"mime_type": mime_type} | ||
| ) | ||
| uris.append(uri) | ||
|
|
||
| else: | ||
| print(f"no thing with name={row['thing_name']} found in db") | ||
| sess.commit() | ||
|
|
||
| return uris | ||
|
|
||
|
|
||
| # ============= EOF ============================================= | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.