fix: make RoboticArm CSV import/export work for arms with fewer than 4 servos - #289
thisisanubhav wants to merge 5 commits into
Conversation
The CSV header always lists Servo1-Servo4, but each row only held one value per servo on the arm, so a timeline exported from a 2-servo arm had short rows that import_timeline_from_csv could not read.
import_timeline_from_csv always returned four angles per timestep, but run_schedule requires exactly one per servo, so a 1-3 servo arm could not run a timeline it imported. Return angles for this arm's servos only, reject files that set an angle for a servo the arm lacks, and treat values missing from short rows as unset.
Exercise export/import for 1-4 servo arms, running an imported timeline, short rows, and rejecting angles for servos the arm lacks.
Reviewer's GuideUpdates RoboticArm CSV serialization for variable servo counts: exports retain the four-servo format, while imports handle legacy short rows, return timelines sized for the current arm, and reject angles for unavailable servos; hardware-free tests cover the supported cases. Sequence diagram for variable-servo timeline CSV round tripsequenceDiagram
participant Caller
participant RoboticArm
participant CSV
Caller->>RoboticArm: export_timeline_to_csv(timeline, filepath)
RoboticArm->>CSV: write Servo1-Servo4 header
RoboticArm->>CSV: write padded rows with null values
Caller->>RoboticArm: import_timeline_from_csv(filepath)
RoboticArm->>CSV: read CSV rows
RoboticArm->>RoboticArm: return angles[:len(servos)]
RoboticArm-->>Caller: timeline sized for current arm
alt unavailable servo has an angle
RoboticArm-->>Caller: raise ValueError
else short row or trailing nulls
RoboticArm-->>Caller: treat missing values as unset
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthrough
ChangesCSV timeline compatibility
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~15 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to Timelines for one- to four-servo arms preserve the existing CSV format, reject malformed data, and remain executable, so no merge-blocking risk is evident. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="pslab/external/motor.py" line_range="134-135" />
<code_context>
- value = row[key]
- if value == "null":
+ for i in range(1, RoboticArm.MAX_SERVOS + 1):
+ value = row.get(f"Servo{i}")
+ # Short rows from older exports leave trailing servos unset.
+ if value in (None, "", "null"):
angles.append(None)
else:
</code_context>
<issue_to_address>
**issue (bug_risk):** A CSV with a missing servo column is treated as a valid short row because `row.get(...)` returns `None` for both an absent header and an absent cell. The importer silently returns unset angles and can discard values supplied in extra fields, whereas the previous implementation rejected a missing header with `KeyError`.
**Triggers:** When the input CSV header omits one or more of the Servo1–Servo4 columns.
**Suggested fix:** Validate that all four servo columns are present in `reader.fieldnames`; only treat missing values in an otherwise valid row as unset.
```suggestion
reader = csv.DictReader(csvfile)
if reader.fieldnames is None or any(
f"Servo{i}" not in reader.fieldnames
for i in range(1, RoboticArm.MAX_SERVOS + 1)
):
raise ValueError("CSV must contain all four servo columns")
for row in reader:
```
</issue_to_address>
### Comment 2
<location path="pslab/external/motor.py" line_range="175" />
<code_context>
writer.writerow(["Timestep", "Servo1", "Servo2", "Servo3", "Servo4"])
for i, row in enumerate(timeline):
+ # Pad to four servos so every row matches the header.
+ row = list(row) + [None] * (RoboticArm.MAX_SERVOS - len(row))
pos = ["null" if val is None else val for val in row]
writer.writerow([i] + pos)
</code_context>
<issue_to_address>
**nitpick (bug_risk):** Rows containing more than four servo values are not truncated or rejected: the negative padding count adds nothing, and `writer.writerow` emits extra fields beyond the four servo columns in the header. Thus the exporter does not guarantee that every row matches the declared format.
**Triggers:** When a caller passes a timeline row with more than `RoboticArm.MAX_SERVOS` values.
**Suggested fix:** Reject rows longer than `RoboticArm.MAX_SERVOS` before writing, or explicitly truncate them if that is the intended contract.
```suggestion
row = list(row)[: RoboticArm.MAX_SERVOS] + [None] * (RoboticArm.MAX_SERVOS - len(row))
```
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: pslab/external/motor.py:135
Importing now requires the Servo1-Servo4 header columns instead of treating a missing column like an unset angle, and exporting rejects timesteps with more than four angles before creating the file.
Fixes #288
Changes
export_timeline_to_csvpads each row withnullup to four servos, so rows always match theServo1–Servo4header. The file format is unchanged.import_timeline_from_csvreturns one angle per servo on this arm, which is whatrun_scheduleexpects. It raisesValueErrorif the file sets an angle for a servo the arm doesn't have, and treats values missing from short rows (written by the old export) as unset.tests/test_robotic_arm.py, which needs no hardware: round trips for 1–4 servo arms, running an imported timeline, short rows, and the rejection case.Testing
pytest tests/test_robotic_arm.py: 8 passed. With the previousmotor.py, 7 of them fail.black --check,flake8,pydocstyleandbanditare clean onpslab/external/motor.py, which is in the lint set.No UI changes.
Summary by Sourcery
Fix RoboticArm CSV timeline handling for arms with fewer than four servos.
Bug Fixes:
Tests:
Summary by CodeRabbit
nullservo values.nullfor unavailable values.