-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add a read-only submit --dry-run #47
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
Changes from all commits
3fc5e69
44a9602
f18d089
40b827f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,9 @@ | |||||
| _DEFAULT_FOLDER_NAME = "Unknown folder" | ||||||
| _DEFAULT_FILE_NAME = "Unknown file" | ||||||
| _CLIENT_INIT_ERROR = "Could not initialize Lighthouse client." | ||||||
| _DRY_RUN_UNVERIFIED_WARNING = ( | ||||||
| "The folder name could not be read; check the folder ID. No submission was sent." | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Warning says the folder name "could not be read", but the unverified path now also fires when the name was read yet unusable for display Since 40b827f,
Suggested change
Reply with |
||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def cmd_submit( | ||||||
|
|
@@ -31,6 +34,7 @@ def cmd_submit( | |||||
| file_path: str, | ||||||
| yes: bool = False, | ||||||
| json_output: bool = False, | ||||||
| dry_run: bool = False, | ||||||
| ) -> int: | ||||||
| """Submit a file to a dropbox folder. | ||||||
|
|
||||||
|
|
@@ -45,6 +49,9 @@ def cmd_submit( | |||||
| folder_name, course_id, course_name, file, submitted_at). | ||||||
|
|
||||||
| Non-interactive / agent-friendly: --yes + --json = only JSON on stdout. | ||||||
|
|
||||||
| ``dry_run`` resolves the same destination with a read-only client and | ||||||
| prints the plan without reading the file body or uploading anything. | ||||||
| """ | ||||||
| # Validate the local input before constructing a client or resolving any | ||||||
| # remote identifiers. A declined submission should not read the file body, | ||||||
|
|
@@ -64,14 +71,14 @@ def cmd_submit( | |||||
|
|
||||||
| # Keep the explicit confirmation requirement for non-interactive callers. | ||||||
| # This check happens after local validation, but before any API work. | ||||||
| if not yes and not sys.stdin.isatty(): | ||||||
| if not dry_run and not yes and not sys.stdin.isatty(): | ||||||
| return _submit_error( | ||||||
| "Refusing to submit without --yes in non-interactive mode. Use --yes flag to confirm.", | ||||||
| json_output, | ||||||
| ) | ||||||
|
|
||||||
| try: | ||||||
| client = LighthouseClient() | ||||||
| client = LighthouseClient(read_only_auth=dry_run) | ||||||
| except Exception: | ||||||
| return _submit_error(_CLIENT_INIT_ERROR, json_output) | ||||||
|
|
||||||
|
|
@@ -82,7 +89,12 @@ def cmd_submit( | |||||
| except Exception as e: | ||||||
| return _submit_error(e, json_output) | ||||||
|
|
||||||
| folder_name = _get_folder_name(client, org_id, folder_id_int) | ||||||
| folder_name, folder_verified = _get_folder_name(client, org_id, folder_id_int) | ||||||
| if dry_run: | ||||||
| return _submit_dry_run( | ||||||
| org_id, course_name, folder_id_int, folder_name, folder_verified, | ||||||
| file_path_obj, display_filename, json_output, | ||||||
| ) | ||||||
|
|
||||||
| # Confirmation prompt (skip with --yes). JSON-mode prompts must not pollute | ||||||
| # stdout; ``input`` is called without a prompt because input() writes its | ||||||
|
|
@@ -157,6 +169,35 @@ def cmd_submit( | |||||
| return 0 | ||||||
|
|
||||||
|
|
||||||
| def _submit_dry_run( | ||||||
| org_id: int, course_name: str, folder_id: int, folder_name: str, verified: bool, | ||||||
| file_path: Path, display_filename: str, json_output: bool, | ||||||
| ) -> int: | ||||||
| """Report the resolved destination; reads only the file's size.""" | ||||||
| try: | ||||||
| file_size = file_path.stat().st_size | ||||||
| except OSError: | ||||||
| return _submit_error("Could not read file.", json_output) | ||||||
| if json_output: | ||||||
| payload: dict[str, object] = { | ||||||
| "dry_run": True, | ||||||
| "course_id": org_id, | ||||||
| "course_name": course_name, | ||||||
| "folder_id": folder_id, | ||||||
| "folder_name": folder_name, | ||||||
| "folder_verified": verified, | ||||||
| "file": {"name": display_filename, "size_bytes": file_size}, | ||||||
| } | ||||||
| if not verified: | ||||||
| payload["warning"] = _DRY_RUN_UNVERIFIED_WARNING | ||||||
| _output_json(payload) | ||||||
| else: | ||||||
| print(f"Would submit to '{folder_name}' in '{course_name}'.\n File: {display_filename} ({file_size} bytes)") | ||||||
| if not verified: | ||||||
| print(f"Warning: {_DRY_RUN_UNVERIFIED_WARNING}") | ||||||
| return 0 | ||||||
|
|
||||||
|
|
||||||
| def _submit_error(message: BaseException | str, json_output: bool) -> int: | ||||||
| """Emit a safe submit failure without double-formatting its diagnostic. | ||||||
|
|
||||||
|
|
@@ -309,15 +350,16 @@ def _positive_folder_id(value: object) -> int | None: | |||||
| return None | ||||||
|
|
||||||
|
|
||||||
| def _get_folder_name(client: LighthouseClient, org_id: int, folder_id: int) -> str: | ||||||
| """Get the name of a dropbox folder by ID.""" | ||||||
| def _get_folder_name(client: LighthouseClient, org_id: int, folder_id: int) -> tuple[str, bool]: | ||||||
| """Get a dropbox folder's display name and whether it was read and usable.""" | ||||||
| try: | ||||||
| detail = client.get_dropbox_folder_detail(org_id, folder_id) | ||||||
| except Exception: | ||||||
| return _DEFAULT_FOLDER_NAME | ||||||
| return _DEFAULT_FOLDER_NAME, False | ||||||
| if not isinstance(detail, dict): | ||||||
| return _DEFAULT_FOLDER_NAME | ||||||
| return _safe_display_name(detail.get("Name"), _DEFAULT_FOLDER_NAME) | ||||||
| return _DEFAULT_FOLDER_NAME, False | ||||||
| name = _safe_display_name(detail.get("Name"), "") | ||||||
| return (name, True) if name else (_DEFAULT_FOLDER_NAME, False) | ||||||
|
|
||||||
|
|
||||||
| def _safe_display_name(value: object, fallback: str) -> str: | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.