feat(signal): add SignalManager to Python SDK#598
Conversation
Code Review — feat(signal): add SignalManager to Python SDKOverviewThis PR adds a Bugs / Correctness
# Current
return terminal[-1] # oldest new terminal (confusing intent)
# Likely intended
return terminal[0] # newest terminal run (most recently started that finished)
seen_ids = {r.id for r in self.get_runs(page=1, page_size=20)}
# then look for runs whose id is not in seen_ids
Style / Convention ViolationsDouble-underscore # All other managers
self._openapi_service = openapi_service # ✓
# Signal manager — should match
self.__openapi_service = openapi_service # ✗Import inside function body ( Missing Missing Items (per CLAUDE.md)No documentation update No automated tests No type annotation for # Add the annotation alongside the assignment
self.signals: RapidataSignalManager = RapidataSignalManager(
openapi_service=self._openapi_service
)Minor
Summary
The overall structure is solid. Fix the |
LinoGiger
left a comment
There was a problem hiding this comment.
this should use the generations and not just send direct api requests
Add SDK support for the Signal Service (recurring audience-job schedules): - RapidataSignalManager exposed as client.signals (create, get, list, get_run). - RapidataSignal with lifecycle methods (pause, resume, delete, trigger, update), get_runs / get_run, and a blocking wait_for_next_run() helper. - SignalRun dataclass with is_terminal / succeeded convenience properties. Built on the autogenerated SignalApi (the signal service is already part of the generated OpenAPI client on main), wired through the same RapidataApiClient as every other manager, so 4xx/5xx surface as RapidataError. Behaviour follows the real backend contract: create() fetches the full signal after the id-only create response; trigger() returns None since the run is spawned asynchronously (observe it via wait_for_next_run()); SignalRun omits owner/created_at fields the run payload does not carry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
aebebd4 to
1783aad
Compare
Code ReviewThis PR adds a clean Signal service layer on top of the already-generated Findings (most severe first)1.
|
Explain what a signal is (audience + job definition + interval, firing a run each tick), when to use one, and how to create, trigger, read results from, and manage signals. Wire the page into the nav and the llms.txt guide list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Code ReviewThis PR adds a clean SDK layer for the new Signal Service — 🔴
|
…nagers - Express the firing interval in hours: create_signal(interval_hours=...), RapidataSignal.interval_hours, and update(interval_hours=...). Converted to the backend's intervalSeconds internally. - Rename manager methods to match the audience/job managers: create -> create_signal, get -> get_signal_by_id, list -> find_signals(name, amount, page), get_run -> get_run_by_id. - Rename RapidataSignal.get_run -> get_run_by_id. - Update the Signals guide to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Code ReviewThis PR adds a well-structured Signal service layer: 1.
|
Reshape the signal API around the SDK's existing job model instead of a parallel 'run' abstraction: - A signal now yields RapidataJob objects: signal.get_jobs() and signal.wait_for_next_job() return live jobs with the full get_results() / get_status() / display_progress_bar() surface. SignalRun (a frozen snapshot) is removed, along with the run-fetch methods. Skipped firings (no job) simply don't appear. - RapidataSignal is a live handle: identity stays cached, but mutable state (name, is_paused, next_run_at, interval_hours, ...) re-fetches on access, so it never goes stale. refresh() is gone (no longer needed). - create_signal accepts objects or ids: audience=RapidataAudience | RapidataFilteredAudience | str, job_definition=RapidataJobDefinition | str, resolved like create_leaderboard does. - Docs rewritten around jobs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Code ReviewThis PR adds the Signal Service layer to the Python SDK — a Confirmed bugs
The PR description's test plan includes: python -c "from rapidata import RapidataSignal, RapidataSignalManager, SignalRun; print('OK')"
started_waiting = datetime.now(timezone.utc) # aware
...
if run.started_at >= started_waiting # run.started_at may be naive
def _ensure_aware(dt: datetime) -> datetime:
return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc)
if _ensure_aware(run.started_at) >= started_waiting and run.audience_job_idPlausible issue
runs = self._openapi_service.signal.signal_api.signal_signal_id_run_get(
self.id, page=1, page_size=20, sort=["-started_at"]
).itemsIf more than 20 runs start between two polls (possible for very high-frequency signals with a longer Minor issues
The docs example passes
|
…prose - Use client.audience.get_audience_by_id(...) in the example instead of creating a fresh (empty) audience. - Remove the 'when to use it' use cases (model monitoring / scheduled evaluation) — a signal re-runs the same job definition on the same datapoints, so those framings were inaccurate. - Cut over-explained / obvious wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <lino@rapidata.ai>
Code ReviewThis PR adds a clean signal management layer on top of the autogenerated SignalApi: a Findings (most severe first)1.
The filter 2.
The generated run model has a 3.
4. Hard-coded
The polling loop always fetches exactly one page of 20 runs. If more than 20 firings accumulate between two poll cycles, runs beyond the first page are never checked and Minor / design notes
|
Code Review — PR #598:
|
| Severity | File | Issue |
|---|---|---|
| 🔴 High | rapidata_signal.py:254 |
Tz-naive/aware comparison → TypeError crash in wait_for_next_job |
| 🟡 Medium | rapidata_signal.py:256 |
fresh_job_ids[-1] returns oldest fresh job, not most recent — wrong after trigger() |
| 🟡 Medium | rapidata_signal.py:58–80 |
6 properties = 6 HTTP calls; no snapshot/batching option for callers |
| 🟡 Medium | rapidata_signal.py:261 |
Timeout can overshoot by poll_interval seconds |
| 🔵 Low | rapidata_signal.py:144 |
Cannot clear description to None via update() |
| 🔵 Low | __init__.py |
SignalRun promised in PR description but absent from public API |
Summary
Adds SDK support for the new Signal Service:
RapidataSignalManagerexposed asclient.signals(CRUD entry point:create,get,list,get_run).RapidataSignalwith lifecycle methods (pause,resume,delete,trigger,update),get_runs/get_run, and a blockingwait_for_next_run(timeout, poll_interval)helper that returns the first new terminal run.SignalRundataclass withis_terminal/succeededconvenience properties.Implementation
Built on the autogenerated
SignalApi— the signal service is already part of the generated OpenAPI client onmain, so this PR is just the hand-written manager / entity / run layer plus its wiring. The manager and entity callopenapi_service.signal.signal_api.*through the sameRapidataApiClientwrapper as every other manager, so 4xx/5xx are converted toRapidataErrorautomatically. (An earlier revision of this branch shipped a raw-HTTP stopgap and regenerated the client itself; both are now obsolete and have been dropped — this branch is rebased ontomain.)The public surface follows the real backend contract:
createreturns only{id, nextRunAt}, soRapidataSignalManager.create()fetches the full signal before returning it.triggerspawns the run asynchronously and returns only the signal id, soRapidataSignal.trigger()returnsNone; usewait_for_next_run()to observe the resulting run.SignalRunomitsowner_id/owner_mail/created_at— the run payload doesn't carry them.list()exposespage/page_size(the endpoint already scopes results to the caller's own + public signals).Test plan
python -c "from rapidata import RapidataClient; print('OK')"python -c "from rapidata.rapidata_client.signal import RapidataSignalManager, RapidataSignal, SignalRun; print('OK')"python -c "from rapidata import RapidataSignal, RapidataSignalManager, SignalRun; print('OK')"pyright src/rapidata/rapidata_client-> 0 errors🔗 Session: https://session-b2a2bd76.poseidon.rapidata.internal/