Skip to content

refactor(jobs): remove jobs-table dependencies from Python SDK (HYBIM-882)#109

Open
etserend wants to merge 6 commits into
mainfrom
feat/HYBIM-882-port-d0dd159
Open

refactor(jobs): remove jobs-table dependencies from Python SDK (HYBIM-882)#109
etserend wants to merge 6 commits into
mainfrom
feat/HYBIM-882-port-d0dd159

Conversation

@etserend

Copy link
Copy Markdown
Contributor

Summary

Port of upstream commit d0dd159 from rungalileo/galileo-python — third and final commit in the HYBIM-882 upstream migration.

  • Delete src/splunk_ao/job_progress.py and src/splunk_ao/jobs.py — both relied on GET /jobs/{job_id} and GET /projects/{id}/runs/{id}/jobs endpoints that the backend removed from its OpenAPI spec. This is the root cause of the PR Update API Client #108 (Update API Client) CI failure.
  • Rewrite Experiment.monitor_progress() to poll experiment status directly via get_status() + tqdm progress bar, removing all jobs-table dependency. The old job_id parameter is kept as a deprecated keyword-only arg emitting DeprecationWarning.
  • Delete tests/test_job_progress.py and tests/test_jobs.py.
  • Add tests/test_experiment_progress.py with 6 tests covering the new polling-based implementation.
  • Update tests/test_experiments.py to remove Jobs.create patches (no longer needed).

Test plan

  • pytest tests/test_experiment_progress.py — 6/6 pass
  • pytest tests/test_experiments.py — 80/80 pass
  • Full suite: 1662 passed (no regressions vs. main)

🤖 Generated with Claude Code

…-882)

Port of upstream rungalileo/galileo-python commit d0dd159.

- Delete src/splunk_ao/job_progress.py and src/splunk_ao/jobs.py; both
  relied on GET /jobs/{job_id} and GET /projects/{id}/runs/{id}/jobs
  endpoints that the backend removed from the OpenAPI spec.
- Rewrite Experiment.monitor_progress() to poll experiment status
  directly via get_status() + tqdm, removing the dependency on the
  jobs table.  The old job_id parameter is preserved as a deprecated
  keyword-only argument that emits DeprecationWarning so callers
  aren't broken silently.
- Delete tests/test_job_progress.py and tests/test_jobs.py.
- Add tests/test_experiment_progress.py with 6 tests for the new
  polling-based monitor_progress() implementation.
- Remove Jobs.create patches from tests/test_experiments.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@fercor-cisco fercor-cisco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 This review was generated by the Astra agent (claude-opus-4-8). It may contain mistakes.

Verdict: request_changes — New polling loop has no failure/timeout exit, so a failed or stalled experiment polls forever — a regression from the old job_progress() which raised on failure.

Follow-ups

Suggested follow-up work that could be tracked as Shortcut stories:

  • src/splunk_ao/shared/experiment_result.py:227-230: ExperimentStatusInfo.is_failed and ExperimentPhaseInfo.is_failed are hardcoded to return False (TODO). Once the API exposes failure status, wiring these up would let monitor_progress and other consumers detect terminal failures properly.
  • tests/test_experiment_progress.py:36-102: Test coverage omits the failure/stall path (understandably, since the current implementation can't terminate on it). Once failure handling/timeout is added, add a test asserting monitor_progress raises or times out rather than hanging on a non-completing experiment.

Comment on lines +1176 to +1181
while not status.is_complete:
new_progress = status.overall_progress
progress_bar.update(new_progress - progress_bar.n)
sleep(poll_interval_seconds)
status = self.get_status()
progress_bar.update(100 - progress_bar.n)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟠 major (bug): This loop's only exit condition is log_generation progress reaching 100%. There is no failure detection and no timeout/max-attempts guard.

ExperimentStatusInfo.is_failed is a hardcoded stub (return False, see shared/experiment_result.py:227-230), and is_complete is purely progress_percent >= 100.0. So if the experiment fails, stalls, or the API never reports 100% (e.g. a completed experiment whose response lacks a log_generation phase, which ExperimentStatusInfo defaults to 0%), while not status.is_complete never terminates and this call hangs indefinitely, polling get_status() forever.

The old job_progress() explicitly raised ValueError on job failure — that safety net is gone. Please add a terminal-failure check and/or a bounded max-wait (e.g. a timeout_seconds param) so the loop can exit and surface an error instead of spinning forever.

🤖 Generated by the Astra agent

Comment thread src/splunk_ao/experiment.py Outdated
etserend and others added 3 commits July 22, 2026 15:16
…ard to monitor_progress

- Add timeout_seconds param (default 1h) so the polling loop exits with
  TimeoutError if the experiment never completes
- Check status.is_failed each iteration and raise RuntimeError immediately
  instead of polling forever on a failed experiment
- Detect a string passed positionally as poll_interval_seconds (legacy
  callers that used the old job_id positional param) and emit a
  DeprecationWarning instead of letting it fail later with TypeError
- Add tests for all three new paths

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…n _make_status

type(status).is_failed = property(...) mutated the ExperimentStatusInfo
class itself, causing all subsequent instances in the same test session
to return is_failed=True — breaking test_raises_timeout_error_when_deadline_exceeded.
Switch _make_status to return a plain MagicMock with explicit attribute
values so tests are fully isolated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mypy correctly rejects isinstance(poll_interval_seconds, str) when the
parameter is typed as float — the check is statically unreachable.
Drop the runtime guard and document the hard break in the docstring instead,
as the reviewer suggested.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@etserend etserend left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both issues addressed in commit 47b4f7b:

🟠 Major (infinite loop): Added timeout_seconds=3600.0 param and status.is_failed check. The loop now raises RuntimeError on failure and TimeoutError if the experiment stalls, matching the safety behaviour of the old job_progress().

🟡 Minor (positional string): Runtime guard was rejected by mypy (isinstance(float, str) is statically unreachable). Documented the hard break in the docstring instead, as suggested.

@fercor-cisco fercor-cisco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 This review was generated by the Astra agent (claude-opus-4-8). It may contain mistakes.

Verdict: approve — Clean, well-tested refactor; the infinite-hang concern is genuinely resolved by the new timeout. One minor dead-code issue in the failure branch worth documenting.

Follow-ups

Suggested follow-up work that could be tracked as Shortcut stories:

  • src/splunk_ao/shared/experiment_result.py:64-67: ExperimentPhaseInfo.is_failed (and the ExperimentStatusInfo.is_failed that wraps it) are hardcoded to return False with a TODO to wire up real failure status once the API exposes it. Until this is implemented, any consumer relying on is_failed (including the new monitor_progress() failure branch) gets no real signal. Pre-existing on main; worth tracking so the monitor_progress failure path becomes effective.

Comment on lines +1198 to +1202
if status.is_failed:
raise RuntimeError(
f"Experiment '{self.id}' entered a failed state. "
"Check the experiment results for details."
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 minor (bug): This if status.is_failed: branch is currently dead code. ExperimentStatusInfo.is_failed delegates to ExperimentPhaseInfo.is_failed, which is a hardcoded stub returning False (see src/splunk_ao/shared/experiment_result.py:64-67 and 227-230, both with a TODO: Check for actual failure status when available in API). So this RuntimeError can never be raised from a real API response.

The consequence: a genuinely failed experiment is not detected fast — the loop keeps polling until timeout_seconds (default 3600s = one hour) elapses and then raises TimeoutError, which is misleading. The old job_progress() raised immediately on job failure with the backend error message; that fast-fail is lost.

The timeout does prevent the true infinite hang, so this isn't a blocker. But since real failure detection depends on API support that doesn't exist yet, I'd suggest making the limitation explicit rather than leaving a branch that looks functional — e.g. a comment noting is_failed is a stub today, or lowering the default timeout_seconds so a stalled/failed experiment surfaces sooner than an hour. Note the unit tests don't catch this because they mock status with a settable is_failed, so the real stubbed property is never exercised.

🤖 Generated by the Astra agent

@fercor-cisco

Copy link
Copy Markdown
Collaborator

Port verification: ✅ correct and complete

Compared this PR against upstream rungalileo/galileo-python commit d0dd159 ("remove jobs-table dependencies from Python SDK", #625) and against the rebrand rules in splunk-ao-migration-tool/README.md.

File-by-file comparison

File Upstream (d0dd159) PR #109 Match
experiment.py +45 / −28 +75 / −31 ✅ core port + hardening
job_progress.py deleted (−119) deleted (−119)
jobs.py deleted (−55) deleted (−55)
test_experiment_progress.py added (+102) added (+125) ✅ + 2 extra tests
test_experiments.py +5 / −54 +5 / −54
test_job_progress.py deleted (−180) deleted (−180)
test_jobs.py deleted (−106) deleted (−106)

Core rewrite: monitor_progress() matches upstream — polls get_status() in a tqdm loop, drops the get_run_scorer_jobs/job_progress dependency, and keeps job_id as a deprecated keyword-only arg emitting DeprecationWarning.

Rebrand compliance (per migration README): all correctly applied — imports use splunk_ao.*, GalileoPythonConfigSplunkAOConfig, GalileoScorers/GalileoMetricsSplunkAOMetrics, tests import from splunk_ao. No stray galileo/Galileo references in the changed code.

No dangling references: The deleted hand-written modules leave no orphans. Remaining job_progress/jobs hits in the tree are generated API-client code (resources/models/job_progress.py, resources/api/jobs/), which upstream also left in place — correctly untouched.

Tests: 88/88 pass locally (test_experiment_progress.py + test_experiments.py), matching the "no regressions" claim.

Two deliberate deviations from upstream (both defensible)

  1. Added hardening (fe42f3e47b4f7b): a timeout_seconds param (raises TimeoutError), an is_failed check (raises RuntimeError), plus two new tests. Additions beyond the upstream commit, made in response to review — they don't compromise fidelity.

  2. _make_status test helper changed shape (b71ea71). Upstream builds a real ExperimentStatusInfo from a mocked API response, so its tests also exercise the overall_progress/is_complete derivation from status.log_generation.progress_percent. This PR replaces that with a plain MagicMock setting those attributes directly, to fix a class-level property mutation that bled across tests. Legitimate fix, but it means the ported tests no longer verify the ExperimentStatusInfo derivation logic — they trust the mock. Minor coverage reduction vs. upstream, not a correctness defect. Isolating a real ExperimentStatusInfo per-instance (instead of mutating the class) would restore that coverage if strict parity is desired.

Net: complete and correct. The only item worth a second look is deviation #2's coverage trade-off.

🤖 Generated with Claude Code

@fercor-cisco

Copy link
Copy Markdown
Collaborator

I created a ticket to address the dead code issue as a follow-up: https://splunk.atlassian.net/browse/HYBIM-920

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.

2 participants