Skip to content

refactor: decouple API handlers from _-prefixed service internals (closes #73)#83

Merged
wpak-ai merged 8 commits into
masterfrom
fix/decouple-api-handlers
May 28, 2026
Merged

refactor: decouple API handlers from _-prefixed service internals (closes #73)#83
wpak-ai merged 8 commits into
masterfrom
fix/decouple-api-handlers

Conversation

@bradjin8
Copy link
Copy Markdown
Collaborator

@bradjin8 bradjin8 commented May 28, 2026

Summary

  • Promotes workspace/CLI helpers in services/workspace_db.py, services/workspace_resolver.py, and services/cli_tabs.py to a documented public API (open_global_db, collect_workspace_entries, lookup_workspace_display_name, get_cli_workspace_tabs, etc.).
  • Updates api/export_api.py and api/workspaces.py so they import only public symbols from services/ — no _-prefixed imports in api/.
  • Keeps module-level _… aliases on service modules and thin re-exports on api/workspaces.py so existing tests and scripts/export.py continue to work without changes.

Closes #73.

Eval: addresses Test 20 (Implementation Hiding) — “Invisible Surface” coupling between HTTP handlers and private service internals (eval baseline).

Details

Layer Change
services/workspace_db collect_workspace_entries, collect_invalid_workspace_ids, build_composer_id_to_workspace_id, open_global_db
services/workspace_resolver lookup_workspace_display_name (distinct from utils.path_helpers.get_workspace_display_name), infer_workspace_name_from_context, determine_project_for_conversation, mapping helpers
services/cli_tabs get_cli_workspace_tabs
api/export_api, api/workspaces Public imports + call sites only
services/workspace_listing, services/workspace_tabs Internal callers aligned with public names

Not in scope: scripts/export.py still uses _… aliases (intentional; follow-up to migrate CLI to public names).

Test plan

  • python -m unittest discover tests -q — 307 passed, 4 skipped
  • Confirm no from services… import _ in api/ (rg 'from services.*import.*_' api/)
  • CI green on PR
  • Spot-check: GET /api/workspaces, GET /api/workspaces/<id>/tabs (IDE + cli: workspace), POST /api/export

Summary by CodeRabbit

  • Refactor
    • Internal workspace, export, and CLI helpers were reorganized and made consistently accessible across the app.
    • Export/manifest I/O and JSON-safe serialization standardized for more reliable exports.
    • CLI workspace tabs handling now returns clearer JSON and status behavior for missing projects or failures.
  • Tests
    • Test suites updated to use the new public helpers while preserving coverage.
  • Impact
    • Improved robustness and error handling; no user-facing feature changes.

Review Change Stack

@bradjin8 bradjin8 self-assigned this May 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Renames underscore-prefixed helpers into public functions and updates all consumers: services, API handlers, export script, utils, and tests; adds docstrings and a documented open_global_db context-manager contract; preserves existing behavior and adjusts export-state error handling.

Changes

De-privatize workspace helpers and update consumers

Layer / File(s) Summary
Workspace DB public helpers
services/workspace_db.py
Adds public helpers collect_workspace_entries, collect_invalid_workspace_ids, build_composer_id_to_workspace_id, and open_global_db (context manager) and updates module comment/docstrings.
Workspace resolver public API
services/workspace_resolver.py
Promotes resolver helpers to public: lookup_workspace_display_name, infer_workspace_name_from_context, get_project_from_file_path, create_project_name_to_workspace_id_map, create_workspace_path_to_id_map, determine_project_for_conversation, infer_invalid_workspace_aliases; updates docstrings and internal uses of open_global_db.
Service consumers (tabs/listing)
services/workspace_tabs.py, services/workspace_listing.py
Update imports and call sites to use public helpers for entry collection/mapping, DB access, display-name lookup, invalid-alias inference, project resolution, and parse-tool-call symbol; adjust ctx_window percent extraction.
CLI tabs public function
services/cli_tabs.py
Renames _get_cli_workspace_tabsget_cli_workspace_tabs and documents JSON response shape and 404/500 behaviors.
API handlers & export script
api/export_api.py, api/workspaces.py, scripts/export.py
API modules and export script updated to call public service helpers; export script exposes configure_cli_logging, json_dump_safe, load_manifest_entries/write_manifest_entries and logs warnings when export state unreadable.
Utils
utils/cli_chat_reader.py, utils/exclusion_rules.py
De-privatize CLI reader helpers (content_to_text, extract_tool_calls, strip_user_info) and rename _tokenize_ruletokenize_rule; update callers.
Tests
tests/*
Many tests updated to import and call the new public helper names (no behavioral assertions changed).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • clean6378-max-it
  • timon0305

"I hopped through underscores, nibbling at leading dashes,
Now functions stand tall, with docstring flashes.
Public names bloom like clover under sun,
Tests still hum softly — the refactor's done.
🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactoring effort: decoupling API handlers from underscore-prefixed service internals.
Linked Issues check ✅ Passed The PR substantially meets all acceptance criteria from #73: removed underscore-prefixed imports from api/ modules, promoted public APIs in services/, documented with docstrings, and all tests pass.
Out of Scope Changes check ✅ Passed All changes are directly within scope: promoting internal helpers to public APIs, updating importers to use public names, and adding docstrings as required by #73.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/decouple-api-handlers

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/export_api.py`:
- Line 117: The context manager call in open_global_db returns a tuple
(global_db, global_db_path) but global_db_path is never used; update the binding
to ignore that value to satisfy RUF059 by changing the with-statement to capture
only global_db and an unused name (e.g., use "as (global_db, _)" or "as
(global_db, _global_db_path)") so the unused second element is clearly
intentionally ignored while leaving the rest of the code unchanged.

In `@services/cli_tabs.py`:
- Around line 22-23: The docstring currently claims the function returns a
``(jsonify({...}), status)`` tuple but the success path returns a plain Flask
Response; update the docstring in services/cli_tabs.py to state the correct
contract as "Response | tuple[Response, int]" (or "flask.Response |
tuple[flask.Response, int]") so it matches the actual return types, referencing
the success return (a Flask Response) and the error tuple form that uses jsonify
and status codes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f0912019-02b3-4ccd-a733-e50fadf8bad8

📥 Commits

Reviewing files that changed from the base of the PR and between 46bda64 and 5cef4f9.

📒 Files selected for processing (7)
  • api/export_api.py
  • api/workspaces.py
  • services/cli_tabs.py
  • services/workspace_db.py
  • services/workspace_listing.py
  • services/workspace_resolver.py
  • services/workspace_tabs.py

Comment thread api/export_api.py Outdated
Comment thread services/cli_tabs.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/cli_tabs.py (1)

15-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the backward-compatible private alias.

_get_cli_workspace_tabs appears removed. That can break existing imports and conflicts with the stated compatibility goal for this refactor.

Suggested minimal patch
 def get_cli_workspace_tabs(workspace_id: str):
@@
     except Exception as e:
         _logger.error(
             "Failed to get CLI workspace tabs for %s: %s (%s)",
             workspace_id,
             e,
             type(e).__name__,
             exc_info=True,
         )
         return jsonify({"error": "Failed to get CLI workspace tabs"}), 500
+
+# Backward-compatible alias for existing callers.
+_get_cli_workspace_tabs = get_cli_workspace_tabs
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/cli_tabs.py` around lines 15 - 149, The refactor removed the
backward-compatible private alias _get_cli_workspace_tabs which may break
callers; restore a module-level alias by assigning _get_cli_workspace_tabs =
get_cli_workspace_tabs (or reintroduce a thin wrapper function named
_get_cli_workspace_tabs that forwards to get_cli_workspace_tabs) so existing
imports continue to work while preserving the new public function
get_cli_workspace_tabs and its behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_workspace_db_special_paths.py`:
- Line 66: The test unpacks a second value from the context manager returned by
open_global_db(ws_root) into the variable name path but never uses it, causing a
Ruff RUF059 lint error; update the unpacked name in the with statement (the
tuple unpack from open_global_db in tests/test_workspace_db_special_paths.py) to
a discard name like _path or _ to mark it intentionally unused while leaving
conn unchanged.

---

Outside diff comments:
In `@services/cli_tabs.py`:
- Around line 15-149: The refactor removed the backward-compatible private alias
_get_cli_workspace_tabs which may break callers; restore a module-level alias by
assigning _get_cli_workspace_tabs = get_cli_workspace_tabs (or reintroduce a
thin wrapper function named _get_cli_workspace_tabs that forwards to
get_cli_workspace_tabs) so existing imports continue to work while preserving
the new public function get_cli_workspace_tabs and its behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6960ee1e-8e18-411d-974c-09e4a11ce50c

📥 Commits

Reviewing files that changed from the base of the PR and between 5cef4f9 and 44d8474.

📒 Files selected for processing (21)
  • api/export_api.py
  • api/workspaces.py
  • scripts/export.py
  • services/cli_tabs.py
  • services/workspace_db.py
  • services/workspace_resolver.py
  • services/workspace_tabs.py
  • tests/test_api_endpoints.py
  • tests/test_cli_chat_reader.py
  • tests/test_cli_tabs.py
  • tests/test_exclusion_rules.py
  • tests/test_invalid_workspace_aliases.py
  • tests/test_models_wired_at_read_sites.py
  • tests/test_project_path_boundary.py
  • tests/test_workspace_assignment_fallback.py
  • tests/test_workspace_db_special_paths.py
  • tests/test_workspace_name_db_errors.py
  • tests/test_workspace_name_inference.py
  • tests/test_workspace_tabs_malformed_nested.py
  • utils/cli_chat_reader.py
  • utils/exclusion_rules.py
💤 Files with no reviewable changes (3)
  • services/workspace_db.py
  • services/workspace_resolver.py
  • api/workspaces.py
✅ Files skipped from review due to trivial changes (3)
  • tests/test_api_endpoints.py
  • tests/test_cli_tabs.py
  • utils/cli_chat_reader.py

Comment thread tests/test_workspace_db_special_paths.py Outdated
…ssues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_workspace_db_special_paths.py` at line 66, The test unpacks a second value from the context manager returned by open_global_db(ws_root) into the variable name path but never uses it, causing a Ruff RUF059 lint error; update the unpacked name in the with statement (the tuple unpack from open_global_db in tests/test_workspace_db_special_paths.py) to a discard name like _path or _ to mark it intentionally unused while leaving conn unchanged.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@bradjin8 bradjin8 requested a review from clean6378-max-it May 28, 2026 15:45
@clean6378-max-it
Copy link
Copy Markdown
Collaborator

Must fix

services/workspace_tabs.py:362 — replace ctx_window.get('percentageRemainingFloat') or ctx_window.get('percentageRemaining') with an explicit is not None check for each field
(or treats 0.0 as falsy; when context is fully consumed, the float is exactly 0.0 and the fallback silently returns percentageRemaining — a different type or None — misrepresenting the context-full state in the UI)

scripts/export.py:42 — remove the unused from utils.tool_parser import parse_tool_call import
(dead import left by the rename sweep; will fail CI linting and misleads readers expecting it to be called)

Should fix

api/export_api.py:50 — narrow except Exception: pass in _get_export_state to except (json.JSONDecodeError, ValueError, OSError) and add a _logger.warning(...) call
(PermissionError and disk-I/O failures are silently swallowed, returning {} and forcing a full re-export the next time the user expects incremental behavior)

api/workspaces.py:146 — wrap the get_cli_workspace_tabs(...) call in a try/except block matching the non-cli branch, returning a structured JSON 500 on failure
(the cli: branch is the only path in workspaces.py that propagates exceptions unhandled; all other failure paths in this file return {"error": ..., "status": 500})

scripts/export.py:200 — replace bare except Exception: pass on the --since=last timestamp parse with _logger.warning('Could not read last export timestamp; defaulting to full export: %s', e)
(a corrupted or schema-changed state file causes a silent full re-export with no log; operators running --since=last cannot distinguish this from a bug)

services/workspace_tabs.py:80 — expand assemble_workspace_tabs docstring to include Args (workspace_id, workspace_path, rules) and Returns (payload field list, meaning of status 404 vs 200)
(Issue 73 acceptance criterion: newly-public functions must have docstrings describing parameters, return types, and error conditions)

services/workspace_listing.py:41 — expand list_workspace_projects docstring to include Args and a Returns section describing the projects list fields and the warnings structure
(same AC requirement; callers must currently read 255 lines of body to learn the return shape)

services/workspace_resolver.py:217 — add Args section to determine_project_for_conversation covering all 8 parameters, particularly composer_id_to_workspace_id=None semantics
(same AC requirement; 8 non-obvious parameters with no documentation)

services/workspace_resolver.py:353 — expand infer_invalid_workspace_aliases docstring to describe the majority-vote algorithm, tie-breaking rule, and when the function returns an empty dict
(same AC requirement; voting semantics are non-obvious from the name)

Nice to have

tests/test_workspace_tabs_malformed_nested.py:204 — update the comment from "Force _parse_tool_call to return None" to "Force parse_tool_call to return None"
(stale reference to the old private name; the patch itself is correct)

api/workspaces.py:51,150 — extract current_app.config.get('EXCLUSION_RULES') or [] into a shared accessor
(same two-line pattern appears in two handlers; a config key rename requires two edits)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
scripts/export.py (1)

199-203: 💤 Low value

Consider narrowing the exception clause (optional).

The logging improvement is good. Static analysis flags the broad except Exception. The expected failure modes here are json.JSONDecodeError, ValueError (from fromisoformat/int), and OSError (permission issues). Narrowing would make the catch more intentional, though the current behavior is already correct.

♻️ Optional: narrow exception types
-        except Exception as e:
+        except (json.JSONDecodeError, ValueError, OSError) as e:
             _logger.warning(
                 "Could not read last export timestamp; defaulting to full export: %s",
                 e,
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export.py` around lines 199 - 203, Narrow the broad except clause
that catches Exception in the block handling the last export timestamp read (the
except Exception as e that logs via _logger.warning) to only catch the expected
failure types: json.JSONDecodeError, ValueError, and OSError; update the except
line to list these specific exceptions and keep the existing logging call
unchanged so you still report the error variable e.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/export.py`:
- Around line 199-203: Narrow the broad except clause that catches Exception in
the block handling the last export timestamp read (the except Exception as e
that logs via _logger.warning) to only catch the expected failure types:
json.JSONDecodeError, ValueError, and OSError; update the except line to list
these specific exceptions and keep the existing logging call unchanged so you
still report the error variable e.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4413702e-bbc6-4ffa-9777-3b128bc2e123

📥 Commits

Reviewing files that changed from the base of the PR and between 3841188 and cac9e53.

📒 Files selected for processing (7)
  • api/export_api.py
  • api/workspaces.py
  • scripts/export.py
  • services/workspace_listing.py
  • services/workspace_resolver.py
  • services/workspace_tabs.py
  • tests/test_workspace_tabs_malformed_nested.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/test_workspace_tabs_malformed_nested.py
  • services/workspace_listing.py
  • services/workspace_tabs.py
  • api/export_api.py
  • services/workspace_resolver.py

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@clean6378-max-it
Copy link
Copy Markdown
Collaborator

@CodeRabbit can you review if my previous 11 comments are resolved or not?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== 1. services/workspace_tabs.py around line 362 (is not None check) ==="
grep -n "percentageRemainingFloat\|percentageRemaining\|is not None" services/workspace_tabs.py | head -20

echo ""
echo "=== 2. scripts/export.py around line 42 (unused import parse_tool_call) ==="
grep -n "parse_tool_call\|tool_parser" scripts/export.py | head -10

echo ""
echo "=== 3. api/export_api.py around line 50 (_get_export_state except clause) ==="
sed -n '40,65p' api/export_api.py

echo ""
echo "=== 4. api/workspaces.py around line 146 (get_cli_workspace_tabs try/except) ==="
sed -n '135,165p' api/workspaces.py

echo ""
echo "=== 5. scripts/export.py around line 200 (--since=last bare except) ==="
sed -n '190,215p' scripts/export.py

echo ""
echo "=== 6. services/workspace_tabs.py around line 80 (assemble_workspace_tabs docstring) ==="
sed -n '70,115p' services/workspace_tabs.py

echo ""
echo "=== 7. services/workspace_listing.py around line 41 (list_workspace_projects docstring) ==="
sed -n '30,60p' services/workspace_listing.py

echo ""
echo "=== 8. services/workspace_resolver.py around line 217 (determine_project_for_conversation Args) ==="
sed -n '205,250p' services/workspace_resolver.py

echo ""
echo "=== 9. services/workspace_resolver.py around line 353 (infer_invalid_workspace_aliases docstring) ==="
sed -n '340,390p' services/workspace_resolver.py

echo ""
echo "=== 10. tests/test_workspace_tabs_malformed_nested.py around line 204 (comment update) ==="
sed -n '195,215p' tests/test_workspace_tabs_malformed_nested.py

echo ""
echo "=== 11. api/workspaces.py lines 51, 150 (EXCLUSION_RULES accessor) ==="
grep -n "EXCLUSION_RULES\|_exclusion_rules" api/workspaces.py | head -20

Length of output: 15433



@clean6378-max-it All 11 comments have been addressed in the current branch. Here's the full resolution status:

# Location Issue Status
1 services/workspace_tabs.py Replace or fallback with explicit is not None checks ✅ Resolved — Lines 377–380 now use if ctx_window.get("percentageRemainingFloat") is not None / elif ctx_window.get("percentageRemaining") is not None
2 scripts/export.py:42 Remove unused from utils.tool_parser import parse_tool_call ✅ Resolved — Import is gone; no parse_tool_call/tool_parser references remain in the file
3 api/export_api.py:50 Narrow except Exception: pass in _get_export_state ✅ Resolved — Now catches (json.JSONDecodeError, ValueError, OSError) and calls _logger.warning(...)
4 api/workspaces.py:146 Wrap get_cli_workspace_tabs(...) in try/except ✅ Resolved — CLI branch now wrapped in try/except Exception returning jsonify({"error": …}), 500
5 scripts/export.py:200 Replace bare except Exception: pass on --since=last parse ✅ Resolved — Now catches (json.JSONDecodeError, ValueError, OSError) and logs "Could not read last export timestamp; defaulting to full export: %s"
6 services/workspace_tabs.py:80 Expand assemble_workspace_tabs docstring ✅ Resolved — Full Args and Returns sections added, including 404 vs 200 semantics
7 services/workspace_listing.py:41 Expand list_workspace_projects docstring ✅ Resolved — Args and Returns sections added, describing projects fields and warnings structure
8 services/workspace_resolver.py:217 Add Args to determine_project_for_conversation ✅ Resolved — All 9 parameters documented, including composer_id_to_workspace_id=None semantics
9 services/workspace_resolver.py:353 Expand infer_invalid_workspace_aliases docstring ✅ Resolved — Majority-vote algorithm described in the summary, full Args section present, Returns documents the {invalid_id: replacement_id} contract
10 tests/test_workspace_tabs_malformed_nested.py:204 Update stale _parse_tool_call comment ✅ Resolved — Comment now reads "Force parse_tool_call to return None"
11 api/workspaces.py:51,150 Extract EXCLUSION_RULES access into shared accessor ✅ Resolved — _exclusion_rules() helper defined at line 43, called at lines 56 and 159

All must-fix, should-fix, and nice-to-have items are resolved. The PR looks good to merge from a comment-resolution perspective.

Copy link
Copy Markdown
Collaborator

@clean6378-max-it clean6378-max-it left a comment

Choose a reason for hiding this comment

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

Should fix

scripts/export.py:271 — log at DEBUG when json.loads fails on a composer row before continue
(silent skip on corrupt composerData rows matches the eval’s “silent leak cascade” pattern; IDE export in api/export_api.py already logs equivalent failures at ERROR)

api/export_api.py:118 — use the same _exclusion_rules() helper as api/workspaces.py (or move it to a shared module)
(EXCLUSION_RULES access is duplicated across two API modules; a config key rename would need two edits)

Nice to have

services/cli_tabs.py:10-12 — move utils.* imports above _logger = logging.getLogger(name)
(PEP 8 import order; no runtime impact)

services/workspace_resolver.py:114-125 — add DEBUG logs in the infer_workspace_name_from_context JSON-parse continue paths
(heuristic loops intentionally skip bad rows; logging would help operators without changing behavior)

api/export_api.py:145 — replace to_epoch_ms(...) or to_epoch_ms(...) or 0 with explicit is not None checks
(epoch 0 is falsy and can mis-order timestamps; very unlikely in practice)

services/cli_tabs.py:40 — pass rules as a parameter like assemble_workspace_tabs instead of reading current_app.config inside the service
(keeps the service layer independent of Flask context; tests already use test_request_context)

@bradjin8 bradjin8 requested a review from clean6378-max-it May 28, 2026 19:28
@bradjin8
Copy link
Copy Markdown
Collaborator Author

Addressed all items:

Should fix

  • scripts/export.py:271 — DEBUG log on (json.JSONDecodeError, ValueError) before continue (aligned with export API error logging, at DEBUG for CLI path).
  • api/export_api.py + api/workspaces.py — Shared exclusion_rules() in new api/flask_config.py; both blueprints use it.

Nice to have

  • services/cli_tabs.py — Utils imports above _logger; rules: list parameter (no current_app); workspaces.py passes exclusion_rules(); tests pass [].
  • services/workspace_resolver.py — DEBUG logs on JSON parse skips in infer_workspace_name_from_context.
  • api/export_api.py:145to_epoch_ms uses explicit is not None (same pattern in scripts/export.py:278).
  • test_workspace_tabs_malformed_nested.py — Already done earlier.

Validation: tests.test_cli_tabs + export exclusion tests OK; mypy clean on touched API/service files.

@clean6378-max-it clean6378-max-it requested a review from wpak-ai May 28, 2026 19:32
@wpak-ai wpak-ai merged commit 87ab7b4 into master May 28, 2026
16 checks passed
@wpak-ai wpak-ai deleted the fix/decouple-api-handlers branch May 28, 2026 21:50
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.

Decouple API handlers from private _-prefixed internals

3 participants