Skip to content
50 changes: 25 additions & 25 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# Confirm the docs build works on PRs touching docs or code
name: ReadTheDocs/Sphinx Validation
on:
pull_request:
paths: ["docs/**", "cwmscli/**", "pyproject.toml"]
push:
branches: [main]
paths: ["docs/**", "cwmscli/**", "pyproject.toml"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install deps
run: |
python -m pip install -U pip
pip install -r docs/requirements.txt
pip install .
- name: Sphinx build (treat warnings as errors)
run: sphinx-build -nW -b html docs docs/_build/html
- name: Link check (optional)
run: sphinx-build -b linkcheck docs docs/_build/linkcheck
# Confirm the docs build works on PRs touching docs or code
name: ReadTheDocs/Sphinx Validation
on:
pull_request:
paths: ["docs/**", "cwmscli/**", "pyproject.toml"]
push:
branches: [main]
paths: ["docs/**", "cwmscli/**", "pyproject.toml"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install deps
run: |
python -m pip install -U pip
pip install -r docs/requirements.txt
pip install .
- name: Sphinx build (treat warnings as errors)
run: sphinx-build -nW -b html docs docs/_build/html
- name: Link check (optional)
run: sphinx-build -b linkcheck docs docs/_build/linkcheck
1 change: 1 addition & 0 deletions cwmscli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ def cli():
cli.add_command(commands_cwms.shefcritimport)
cli.add_command(commands_cwms.csv2cwms_cmd)
cli.add_command(commands_cwms.blob_group)
cli.add_command(commands_cwms.clob_group)
122 changes: 17 additions & 105 deletions cwmscli/commands/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import os
import re
import sys
from typing import Optional, Sequence
from typing import Optional, Sequence, Union

import cwms
import pandas as pd
import requests

from cwmscli.utils import get_api_key
from cwmscli.utils import get_api_key, has_invalid_chars
from cwmscli.utils.deps import requires

# used to rebuild data URL for images
Expand Down Expand Up @@ -89,107 +89,6 @@ def _save_base64(
return dest


def store_blob(**kwargs):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for removing this! I missed it

file_data = kwargs.get("file_data")
blob_id = kwargs.get("blob_id", "").upper()
# Attempt to determine what media type should be used for the mime-type if one is not presented based on the file extension
media = kwargs.get("media_type") or get_media_type(kwargs.get("input_file"))

logging.debug(
f"Office: {kwargs.get('office')} Output ID: {blob_id} Media: {media}"
)

blob = {
"office-id": kwargs.get("office"),
"id": blob_id,
"description": json.dumps(kwargs.get("description")),
"media-type-id": media,
"value": base64.b64encode(file_data).decode("utf-8"),
}

params = {"fail-if-exists": not kwargs.get("overwrite")}

if kwargs.get("dry_run"):
logging.info(
f"--dry-run enabled. Would POST to {kwargs.get('api_root')}/blobs with params={params}"
)
logging.info(
f"Blob payload summary: office-id={kwargs.get('office')}, id={blob_id}, media={media}",
)
logging.info(
json.dumps(
{
"url": f"{kwargs.get('api_root')}blobs",
"params": params,
"blob": {**blob, "value": f"<base64:{len(blob['value'])} chars>"},
},
indent=2,
)
)
sys.exit(0)

try:
cwms.store_blobs(blob, fail_if_exists=kwargs.get("overwrite"))
logging.info(f"Successfully stored blob with ID: {blob_id}")
logging.info(
f"View: {kwargs.get('api_root')}blobs/{blob_id}?office={kwargs.get('office')}"
)
except requests.HTTPError as e:
# Include response text when available
detail = getattr(e.response, "text", "") or str(e)
logging.error(f"Failed to store blob (HTTP): {detail}")
sys.exit(1)
except Exception as e:
logging.error(f"Failed to store blob: {e}")
sys.exit(1)


def retrieve_blob(**kwargs):
blob_id = kwargs.get("blob_id", "").upper()
if not blob_id:
logging.warning(
"Valid blob_id required to download a blob. cwms-cli blob download --blob-id=myid. Run the list directive to see options for your office."
)
sys.exit(0)
logging.debug(f"Office: {kwargs.get('office')} Blob ID: {blob_id}")
try:
blob = cwms.get_blob(
office_id=kwargs.get("office"),
blob_id=blob_id,
)
logging.info(
f"Successfully retrieved blob with ID: {blob_id}",
)
_save_base64(blob, dest=blob_id)
logging.info(f"Downloaded blob to: {blob_id}")
except requests.HTTPError as e:
detail = getattr(e.response, "text", "") or str(e)
logging.error(f"Failed to retrieve blob (HTTP): {detail}")
sys.exit(1)
except Exception as e:
logging.error(f"Failed to retrieve blob: {e}")
sys.exit(1)


def delete_blob(**kwargs):
blob_id = kwargs.get("blob_id").upper()
logging.debug(f"Office: {kwargs.get('office')} Blob ID: {blob_id}")

try:
# cwms.delete_blob(
# office_id=kwargs.get("office"),
# blob_id=kwargs.get("blob_id").upper(),
# )
logging.info(f"Successfully deleted blob with ID: {blob_id}")
except requests.HTTPError as e:
details = getattr(e.response, "text", "") or str(e)
logging.error(f"Failed to delete blob (HTTP): {details}")
sys.exit(1)
except Exception as e:
logging.error(f"Failed to delete blob: {e}")
sys.exit(1)


def list_blobs(
office: Optional[str] = None,
blob_id_like: Optional[str] = None,
Expand Down Expand Up @@ -294,7 +193,12 @@ def upload_cmd(
try:
cwms.store_blobs(blob, fail_if_exists=not overwrite)
logging.info(f"Uploaded blob: {blob_id_up}")
logging.info(f"View: {api_root}blobs/{blob_id_up}?office={office}")
if has_invalid_chars(blob_id_up):
logging.info(
f"View: {api_root}blobs/ignored?blob-id={blob_id_up}&office={office}"
)
else:
logging.info(f"View: {api_root}blobs/{blob_id_up}?office={office}")
except requests.HTTPError as e:
detail = getattr(e.response, "text", "") or str(e)
logging.error(f"Failed to upload (HTTP): {detail}")
Expand Down Expand Up @@ -414,4 +318,12 @@ def list_cmd(
else:
# Friendly console preview
with pd.option_context("display.max_rows", 500, "display.max_columns", None):
logging.info(df.to_string(index=False))
# Left-align all columns
logging.info(
"\n"
+ df.apply(
lambda s: (s := s.astype(str).str.strip()).str.ljust(
s.str.len().max()
)
).to_string(index=False, justify="left")
)
Loading