Skip to content

Adding data (aka json) to the py_lib#1318

Merged
kixelated merged 12 commits intomoq-dev:mainfrom
lullabee:claire/pylib_rawdata
Apr 17, 2026
Merged

Adding data (aka json) to the py_lib#1318
kixelated merged 12 commits intomoq-dev:mainfrom
lullabee:claire/pylib_rawdata

Conversation

@lullabee
Copy link
Copy Markdown
Contributor

@lullabee lullabee commented Apr 17, 2026

The change adds track- and group-level raw publishing and consumption across the Rust FFI and Python wrapper: new UniFFI types MoqTrackProducer, MoqGroupProducer, MoqTrackConsumer, MoqGroupConsumer and a MoqBroadcastConsumer::subscribe were introduced; MoqBroadcastProducer gained publish and a UniFFI consume. The Python package exposes GroupProducer, TrackProducer, GroupConsumer, TrackConsumer, extends BroadcastProducer/BroadcastConsumer with publish/subscribe and corresponding consume helpers, adds async iterators for groups/frames, includes new unit tests, and bumps version to 0.0.3.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 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

The change adds raw track- and group-level publishing and consumption across the Rust FFI and Python wrapper. New UniFFI types MoqTrackProducer, MoqGroupProducer, MoqTrackConsumer, and MoqGroupConsumer were introduced; MoqBroadcastProducer gained publish_track and consume methods; MoqBroadcastConsumer gained subscribe_track. The Python package now exposes GroupProducer, TrackProducer, GroupConsumer, TrackConsumer, extends BroadcastProducer/BroadcastConsumer with corresponding helpers, adds async iterators for groups/frames, new tests, and increments the package version to 0.0.3.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title 'Adding data (aka json) to the py_lib' is vague and does not accurately reflect the main changes. The changeset primarily adds track- and group-level raw publishing and consumption APIs across both Rust FFI and Python layers, with new consumer/producer classes. The title's reference to 'data (aka json)' and 'py_lib' does not convey the actual technical changes being made. Revise the title to be more specific and descriptive, such as 'Add track and group producer/consumer APIs for raw data publishing and consumption' to better represent the primary changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description directly and comprehensively relates to the changeset. It accurately describes the addition of track- and group-level raw publishing and consumption across Rust FFI and Python layers, mentions the new UniFFI types and Python wrapper classes, and references the version bump and unit tests.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (2)
rs/moq-ffi/src/consumer.rs (1)

125-137: Empty/extra-frame groups are silently dropped — confirm this is intended.

RawInner::next only returns the first frame of each group; if a group contains zero frames it's skipped with continue, and if it contains more than one frame the additional frames are discarded (the next call gets a fresh group). This matches the producer (one-frame-per-group), but if a remote peer ever publishes raw tracks differently (e.g., multiple frames per group, as moq-boy may do), receivers will silently lose data.

Consider either:

  • documenting the one-frame-per-group contract on subscribe_raw, or
  • draining all frames in the current group before advancing to the next group.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-ffi/src/consumer.rs` around lines 125 - 137, RawInner::next currently
returns only the first frame from a group and discards any additional frames
(skipping empty groups), which can silently drop data; update RawInner::next so
that after retrieving the first frame from a group (via
group.read_frame().await?), you drain the rest of the frames in that same group
before returning (e.g., loop calling group.read_frame().await? until it yields
None) so no frames from the group are silently left behind, keeping references
to RawInner::next, track.next_group(), and group.read_frame() to locate the
change.
py/moq-lite/moq_lite/subscribe.py (1)

41-45: Optional: bytes(frame) is likely redundant.

MoqRawConsumer.next() returns Vec<u8> which UniFFI already maps to Python bytes, so bytes(frame) performs an unnecessary copy. You can return frame directly (matching how MediaConsumer.__anext__ returns the frame as-is). Not a correctness issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@py/moq-lite/moq_lite/subscribe.py` around lines 41 - 45, In __anext__ of the
subscriber (async def __anext__(self) -> bytes) avoid the redundant bytes(frame)
copy: since MoqRawConsumer.next() already returns a bytes-like object (UniFFI
maps Vec<u8> to Python bytes), simply return frame directly; update the return
statement in __anext__ to return frame (mirroring MediaConsumer.__anext__) and
remove the extra conversion to eliminate the unnecessary allocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rs/moq-ffi/src/producer.rs`:
- Around line 109-116: The write_frame implementation for MoqRawProducer lacks
the runtime enter guard and can panic when calling TrackProducer::append_group
(which uses tokio::time::Instant); add the same RUNTIME.enter() guard used in
publish_raw/finish/MoqMediaProducer::write_frame (e.g., let _rt =
RUNTIME.enter(); at the start of pub fn write_frame(&self, ...) ) so the tokio
reactor is active for calls like track.append_group(), then proceed with locking
self.inner and the existing append_group/write_frame/finish sequence.

---

Nitpick comments:
In `@py/moq-lite/moq_lite/subscribe.py`:
- Around line 41-45: In __anext__ of the subscriber (async def __anext__(self)
-> bytes) avoid the redundant bytes(frame) copy: since MoqRawConsumer.next()
already returns a bytes-like object (UniFFI maps Vec<u8> to Python bytes),
simply return frame directly; update the return statement in __anext__ to return
frame (mirroring MediaConsumer.__anext__) and remove the extra conversion to
eliminate the unnecessary allocation.

In `@rs/moq-ffi/src/consumer.rs`:
- Around line 125-137: RawInner::next currently returns only the first frame
from a group and discards any additional frames (skipping empty groups), which
can silently drop data; update RawInner::next so that after retrieving the first
frame from a group (via group.read_frame().await?), you drain the rest of the
frames in that same group before returning (e.g., loop calling
group.read_frame().await? until it yields None) so no frames from the group are
silently left behind, keeping references to RawInner::next, track.next_group(),
and group.read_frame() to locate the change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6ec4042-f766-4728-9022-d80ae3d675b2

📥 Commits

Reviewing files that changed from the base of the PR and between d351360 and b0e0e08.

📒 Files selected for processing (8)
  • demo/justfile
  • py/moq-lite/moq_lite/__init__.py
  • py/moq-lite/moq_lite/publish.py
  • py/moq-lite/moq_lite/subscribe.py
  • py/moq-lite/pyproject.toml
  • py/moq-lite/tests/test_local.py
  • rs/moq-ffi/src/consumer.rs
  • rs/moq-ffi/src/producer.rs

Comment thread rs/moq-ffi/src/producer.rs
@kixelated
Copy link
Copy Markdown
Collaborator

@Qizot 👀

Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rs/moq-dummy-raw/src/main.rs`:
- Around line 43-45: The CLI field rate (struct field rate parsed by clap) can
be zero or negative which makes the downstream expression
Duration::from_secs_f64(1.0 / cli.rate) produce non-finite/negative input and
panic; validate or constrain rate immediately after parsing (or via clap
validator) to ensure cli.rate > 0.0 and return a user-friendly error for
non-positive values so Duration::from_secs_f64 is only called with a finite
positive value.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2db43256-5fb9-442b-a2c7-f6dcf9254327

📥 Commits

Reviewing files that changed from the base of the PR and between b0e0e08 and 9ac7a42.

⛔ Files ignored due to path filters (10)
  • Cargo.lock is excluded by !**/*.lock
  • demo/relay/heaptrack.moq-relay.75889.zst is excluded by !**/*.zst
  • demo/relay/heaptrack.moq-relay.76064.zst is excluded by !**/*.zst
  • demo/relay/heaptrack.moq-relay.76598.zst is excluded by !**/*.zst
  • demo/relay/heaptrack.moq-relay.79333.zst is excluded by !**/*.zst
  • demo/relay/heaptrack.moq-relay.82171.zst is excluded by !**/*.zst
  • demo/relay/heaptrack.moq-relay.86157.zst is excluded by !**/*.zst
  • heaptrack.just.36493.zst is excluded by !**/*.zst
  • heaptrack.just.49492.zst is excluded by !**/*.zst
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml
  • rs/moq-dummy-raw/Cargo.toml
  • rs/moq-dummy-raw/src/main.rs
✅ Files skipped from review due to trivial changes (2)
  • Cargo.toml
  • rs/moq-dummy-raw/Cargo.toml

Comment thread rs/moq-dummy-raw/src/main.rs Outdated
Comment on lines +43 to +45
/// Frames per second.
#[arg(long, default_value_t = 20.0)]
rate: f64,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validate rate to avoid panic on zero/negative input.

clap accepts any f64, so --rate 0 or a negative value flows into line 75: Duration::from_secs_f64(1.0 / cli.rate). 1.0 / 0.0 yields inf, and Duration::from_secs_f64 panics on non-finite or negative values, killing the binary with an unhelpful message. Consider constraining the parsed range or rejecting invalid values up front.

🛡️ Proposed fix
 	/// Frames per second.
-	#[arg(long, default_value_t = 20.0)]
+	#[arg(long, default_value_t = 20.0, value_parser = parse_positive_rate)]
 	rate: f64,
 }
+
+fn parse_positive_rate(s: &str) -> Result<f64, String> {
+	let v: f64 = s.parse().map_err(|e: std::num::ParseFloatError| e.to_string())?;
+	if v.is_finite() && v > 0.0 {
+		Ok(v)
+	} else {
+		Err(format!("rate must be a positive finite number, got {v}"))
+	}
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Frames per second.
#[arg(long, default_value_t = 20.0)]
rate: f64,
/// Frames per second.
#[arg(long, default_value_t = 20.0, value_parser = parse_positive_rate)]
rate: f64,
}
fn parse_positive_rate(s: &str) -> Result<f64, String> {
let v: f64 = s.parse().map_err(|e: std::num::ParseFloatError| e.to_string())?;
if v.is_finite() && v > 0.0 {
Ok(v)
} else {
Err(format!("rate must be a positive finite number, got {v}"))
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-dummy-raw/src/main.rs` around lines 43 - 45, The CLI field rate
(struct field rate parsed by clap) can be zero or negative which makes the
downstream expression Duration::from_secs_f64(1.0 / cli.rate) produce
non-finite/negative input and panic; validate or constrain rate immediately
after parsing (or via clap validator) to ensure cli.rate > 0.0 and return a
user-friendly error for non-positive values so Duration::from_secs_f64 is only
called with a finite positive value.

Restructures the raw track FFI to mirror moq-lite's GroupProducer /
GroupConsumer pattern: callers get a group handle from append_group() /
next_group() and then write_frame() / read_frame() on it. The flat
frame iterator is gone — each group is addressable, sequence-numbered,
and independently consumable.

- Add MoqRawGroupProducer and MoqRawGroupConsumer, each exposing sequence()
- Add consume() on MoqBroadcastProducer / MoqRawProducer / MoqRawGroupProducer for direct local pub/sub
- Add RUNTIME.enter() guard to MoqRawProducer::write_frame for consistency
- Drop the redundant bytes(frame) cast in the Python consumer
- Expand Python test suite to 24 tests covering sequences, parallel
  groups, multi-frame groups, empty payloads, error paths, and the new
  direct-consume flows
- Remove the moq-dummy-raw debug crate, committed heaptrack .zst profiles,
  and the unrelated demo/justfile serve recipe

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@py/moq-lite/tests/test_local.py`:
- Around line 282-305: Replace the broad pytest.raises(Exception) assertions in
tests test_raw_group_finish_twice_fails and
test_raw_track_write_after_finish_fails with a narrow check for the concrete FFI
error (or at minimum a message match); specifically, when calling
group.finish(), track.finish(), group.write_frame(b"too late"),
track.write_frame(b"late") and track.append_group() change
pytest.raises(Exception) to pytest.raises(moq.MoqError) (or the actual exception
class raised by the Rust FFI) or use pytest.raises(<ExceptionClass>,
match="closed|finished") so the tests assert the expected closed/finished error
from BroadcastProducer.publish_raw, append_group, finish, and write_frame.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23bfc49b-713c-42ed-a126-3dae9fd1c517

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac7a42 and 495d1ad.

📒 Files selected for processing (7)
  • py/moq-lite/moq_lite/__init__.py
  • py/moq-lite/moq_lite/publish.py
  • py/moq-lite/moq_lite/subscribe.py
  • py/moq-lite/tests/test_local.py
  • rs/moq-ffi/src/consumer.rs
  • rs/moq-ffi/src/origin.rs
  • rs/moq-ffi/src/producer.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • py/moq-lite/moq_lite/init.py
  • rs/moq-ffi/src/consumer.rs
  • rs/moq-ffi/src/producer.rs

Comment on lines +282 to +305
with pytest.raises(Exception):
group.write_frame(b"too late")


def test_raw_group_finish_twice_fails():
broadcast = moq.BroadcastProducer()
track = broadcast.publish_raw("t")
group = track.append_group()
group.finish()

with pytest.raises(Exception):
group.finish()


def test_raw_track_write_after_finish_fails():
broadcast = moq.BroadcastProducer()
track = broadcast.publish_raw("t")
track.finish()

with pytest.raises(Exception):
track.write_frame(b"late")

with pytest.raises(Exception):
track.append_group()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Narrow the pytest.raises exception types.

Ruff B017 flags these four pytest.raises(Exception) calls: they will pass on any exception (including unrelated AttributeError/TypeError/import errors), which hides regressions. Prefer the concrete FFI error class (e.g. the MoqError surfaced by the Rust layer, or whatever the FFI raises on closed/finished state) or at minimum match= on the message.

🔧 Suggested tightening (adjust to the actual exception type)
-    with pytest.raises(Exception):
+    with pytest.raises(Exception, match=r"(?i)finish|closed|done"):
         group.write_frame(b"too late")

Applied similarly to lines 292, 301, 304. Same treatment would also benefit the pre-existing test_unknown_format (line 87) and test_finish_closes_producer (line 216), though those are outside this diff.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 282-282: Do not assert blind exception: Exception

(B017)


[warning] 292-292: Do not assert blind exception: Exception

(B017)


[warning] 301-301: Do not assert blind exception: Exception

(B017)


[warning] 304-304: Do not assert blind exception: Exception

(B017)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@py/moq-lite/tests/test_local.py` around lines 282 - 305, Replace the broad
pytest.raises(Exception) assertions in tests test_raw_group_finish_twice_fails
and test_raw_track_write_after_finish_fails with a narrow check for the concrete
FFI error (or at minimum a message match); specifically, when calling
group.finish(), track.finish(), group.write_frame(b"too late"),
track.write_frame(b"late") and track.append_group() change
pytest.raises(Exception) to pytest.raises(moq.MoqError) (or the actual exception
class raised by the Rust FFI) or use pytest.raises(<ExceptionClass>,
match="closed|finished") so the tests assert the expected closed/finished error
from BroadcastProducer.publish_raw, append_group, finish, and write_frame.

Renames the FFI/Python track API to match moq-lite's TrackProducer /
TrackConsumer naming. Since the moq-lite layer is always "arbitrary
bytes," the Raw prefix is redundant — media is the specialization, not
the other way around.

- MoqRawProducer / MoqRawConsumer -> MoqTrackProducer / MoqTrackConsumer
- MoqRawGroupProducer / MoqRawGroupConsumer -> MoqGroupProducer / MoqGroupConsumer
- Python RawProducer / RawConsumer -> TrackProducer / TrackConsumer
- Python RawGroupProducer / RawGroupConsumer -> GroupProducer / GroupConsumer
- publish_raw / subscribe_raw -> publish / subscribe

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (2)
rs/moq-ffi/src/producer.rs (1)

82-93: Track priority is hardcoded to 0.

moq_lite::Track { name, priority: 0 } hardcodes priority, matching subscribe_media/subscribe on the consumer side. That's fine as a first cut, but note that once any caller wants non-default prioritization they'll need a breaking signature change here. Consider whether a publish_with_priority(name, priority) overload (or an optional priority: Option<u8>) is worth adding now to avoid churn later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-ffi/src/producer.rs` around lines 82 - 93, The publish method
currently hardcodes track priority to 0 (moq_lite::Track { name, priority: 0 }),
which prevents callers from choosing non-default prioritization; add an overload
like publish_with_priority(name: String, priority: u8) (or change publish to
accept Option<u8>) so callers can supply a priority, propagate that value into
moq_lite::Track when creating the track inside publish/publish_with_priority,
and keep the existing publish(name) as a convenience wrapper delegating to the
new method to avoid breaking callers; update construction locations where
MoqTrackProducer is created so they call the new API.
py/moq-lite/tests/test_local.py (1)

231-486: Test names still use the old raw_ prefix.

The commits in this PR explicitly dropped the Raw prefix from the FFI and Python APIs (publish_raw/subscribe_rawpublish/subscribe, RawProducerTrackProducer, etc.). The new tests, however, retain test_raw_* names (e.g. test_raw_append_group_sequence_increments, test_raw_publish_consume, test_raw_multi_frame_group, …), which now misrepresents what's being tested — these exercise the regular track API, not a separate "raw" surface. Consider renaming to test_track_* / test_group_* (or similar) for consistency with the final naming.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@py/moq-lite/tests/test_local.py` around lines 231 - 486, Rename tests that
still use the old "raw" naming to reflect the new Track/Group API: change
function names like test_raw_append_group_sequence_increments →
test_track_append_group_sequence_increments, test_raw_publish_consume →
test_track_publish_consume, test_raw_multi_frame_group → test_group_multi_frame
(or similar), and update all other test functions prefixed with test_raw_ to
test_track_ or test_group_ as appropriate so names match the current APIs (refer
to symbol names BroadcastProducer.publish, TrackProducer.append_group, and
group.consume in the file to locate related tests and ensure names reflect
"track" or "group" semantics).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@py/moq-lite/tests/test_local.py`:
- Around line 231-486: Rename tests that still use the old "raw" naming to
reflect the new Track/Group API: change function names like
test_raw_append_group_sequence_increments →
test_track_append_group_sequence_increments, test_raw_publish_consume →
test_track_publish_consume, test_raw_multi_frame_group → test_group_multi_frame
(or similar), and update all other test functions prefixed with test_raw_ to
test_track_ or test_group_ as appropriate so names match the current APIs (refer
to symbol names BroadcastProducer.publish, TrackProducer.append_group, and
group.consume in the file to locate related tests and ensure names reflect
"track" or "group" semantics).

In `@rs/moq-ffi/src/producer.rs`:
- Around line 82-93: The publish method currently hardcodes track priority to 0
(moq_lite::Track { name, priority: 0 }), which prevents callers from choosing
non-default prioritization; add an overload like publish_with_priority(name:
String, priority: u8) (or change publish to accept Option<u8>) so callers can
supply a priority, propagate that value into moq_lite::Track when creating the
track inside publish/publish_with_priority, and keep the existing publish(name)
as a convenience wrapper delegating to the new method to avoid breaking callers;
update construction locations where MoqTrackProducer is created so they call the
new API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcfdb2e5-01de-47d8-bf7f-23d6c42c636f

📥 Commits

Reviewing files that changed from the base of the PR and between 495d1ad and ab9c96d.

📒 Files selected for processing (6)
  • py/moq-lite/moq_lite/__init__.py
  • py/moq-lite/moq_lite/publish.py
  • py/moq-lite/moq_lite/subscribe.py
  • py/moq-lite/tests/test_local.py
  • rs/moq-ffi/src/consumer.rs
  • rs/moq-ffi/src/producer.rs
✅ Files skipped from review due to trivial changes (1)
  • py/moq-lite/moq_lite/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • py/moq-lite/moq_lite/subscribe.py

@lullabee lullabee changed the title Adding raw data (aka json) to the py_lib Adding data (aka json) to the py_lib Apr 17, 2026
kixelated and others added 5 commits April 17, 2026 10:16
- moq-lite: TrackConsumer::read_frame() helper for one-frame-per-group
  tracks (moq-boy status/command pattern). Skips empty groups and
  returns None at end-of-track.
- moq-ffi / py: MoqBroadcastProducer::publish -> publish_track,
  MoqBroadcastConsumer::subscribe -> subscribe_track,
  MoqTrackConsumer::next_group -> recv_group (arrival order). The name
  change tracks the moq-lite rename happening on a separate branch.
- moq-ffi / py: MoqTrackConsumer::read_frame() exposed as a thin wrapper.
- py: TrackConsumer.recv_group explicit method alongside async iteration;
  __anext__ now delegates to recv_group.
- tests: 3 new cases for read_frame (one-per-group, multi-frame skip,
  end-of-track).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exposes both arrival-order (recv_group) and sequence-order (next_group)
variants on the FFI and Python track consumer APIs. next_group is wired
to moq-lite's current next_group (arrival order) for now; will switch to
the sequence-ordered method once it lands on moq-lite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- moq-ffi TrackInner::recv_group now calls moq_lite::TrackConsumer::recv_group
  (was calling the deprecated next_group). TrackInner::next_group now calls
  next_group_ordered, which is the sequence-ordered variant just landed on main.
- moq-lite read_frame helper switched to recv_group; it only needs arrival order
  for its one-frame-per-group usage.
- justfile: ci recipe now builds moq-ffi from source before pyright and passes
  --no-sync so uv doesn't replace it with the registry build. Same for pytest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…v#1326

Drops my loop-over-recv_group implementation in favor of the upstream
scanner (poll_read_frame on State), which correctly skips past stalled
earlier groups instead of blocking on them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated force-pushed the claire/pylib_rawdata branch from ab9c96d to b30b6c1 Compare April 17, 2026 21:38
@kixelated kixelated enabled auto-merge (squash) April 17, 2026 21:42
Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
py/moq-lite/tests/test_local.py (1)

282-305: ⚠️ Potential issue | 🟡 Minor

Narrow the pytest.raises exception types (Ruff B017, still unaddressed).

Ruff still flags lines 282, 292, 301, and 304 as B017. pytest.raises(Exception) will swallow unrelated failures (e.g., AttributeError from a typo, import errors) and hide regressions in the new raw-track lifecycle code these tests are meant to guard. Tighten to the concrete FFI error class or at minimum add match= on the expected "closed/finished" message.

🔧 Suggested tightening (adjust to the actual exception type)
-    with pytest.raises(Exception):
+    with pytest.raises(Exception, match=r"(?i)finish|closed|done"):
         group.write_frame(b"too late")

Apply the same pattern to lines 292, 301, and 304.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@py/moq-lite/tests/test_local.py` around lines 282 - 305, The tests use
pytest.raises(Exception) which is too broad; update the four raises in
test_raw_group_finish_twice_fails and test_raw_track_write_after_finish_fails to
assert a specific error by replacing Exception with the concrete FFI error class
used by your library (e.g., the FFI/interop error type) or, if uncertain, add a
match="closed|finished" argument to pytest.raises to narrow the expectation.
Locate the assertions around group.finish() in test_raw_group_finish_twice_fails
and around track.write_frame(...) and track.append_group() in
test_raw_track_write_after_finish_fails (objects created via
BroadcastProducer.publish_track, track.append_group, group.finish, track.finish)
and tighten each pytest.raises accordingly.
🧹 Nitpick comments (1)
rs/moq-ffi/src/consumer.rs (1)

156-182: Use MoqGroupConsumer::new to avoid duplicating the constructor logic.

Both recv_group and next_group reproduce the sequence + Task::new(GroupInner { group }) initialization inline, while MoqGroupConsumer::new (defined at lines 213–220) does exactly that. Delegating keeps a single source of truth if the struct grows new fields.

♻️ Proposed refactor
 	pub async fn recv_group(&self) -> Result<Option<Arc<MoqGroupConsumer>>, MoqError> {
 		self.task
 			.run(|mut state| async move {
-				Ok(state.recv_group().await?.map(|group| {
-					Arc::new(MoqGroupConsumer {
-						sequence: group.info.sequence,
-						task: Task::new(GroupInner { group }),
-					})
-				}))
+				Ok(state.recv_group().await?.map(|group| Arc::new(MoqGroupConsumer::new(group))))
 			})
 			.await
 	}

 	pub async fn next_group(&self) -> Result<Option<Arc<MoqGroupConsumer>>, MoqError> {
 		self.task
 			.run(|mut state| async move {
-				Ok(state.next_group().await?.map(|group| {
-					Arc::new(MoqGroupConsumer {
-						sequence: group.info.sequence,
-						task: Task::new(GroupInner { group }),
-					})
-				}))
+				Ok(state.next_group().await?.map(|group| Arc::new(MoqGroupConsumer::new(group))))
 			})
 			.await
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rs/moq-ffi/src/consumer.rs` around lines 156 - 182, The two methods
recv_group and next_group duplicate the MoqGroupConsumer construction; update
both to call MoqGroupConsumer::new(group) inside the map instead of manually
constructing with sequence and Task::new(GroupInner { group }), so the map
becomes .map(|group| Arc::new(MoqGroupConsumer::new(group))) thereby
centralizing initialization logic in MoqGroupConsumer::new.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@py/moq-lite/tests/test_local.py`:
- Around line 282-305: The tests use pytest.raises(Exception) which is too
broad; update the four raises in test_raw_group_finish_twice_fails and
test_raw_track_write_after_finish_fails to assert a specific error by replacing
Exception with the concrete FFI error class used by your library (e.g., the
FFI/interop error type) or, if uncertain, add a match="closed|finished" argument
to pytest.raises to narrow the expectation. Locate the assertions around
group.finish() in test_raw_group_finish_twice_fails and around
track.write_frame(...) and track.append_group() in
test_raw_track_write_after_finish_fails (objects created via
BroadcastProducer.publish_track, track.append_group, group.finish, track.finish)
and tighten each pytest.raises accordingly.

---

Nitpick comments:
In `@rs/moq-ffi/src/consumer.rs`:
- Around line 156-182: The two methods recv_group and next_group duplicate the
MoqGroupConsumer construction; update both to call MoqGroupConsumer::new(group)
inside the map instead of manually constructing with sequence and
Task::new(GroupInner { group }), so the map becomes .map(|group|
Arc::new(MoqGroupConsumer::new(group))) thereby centralizing initialization
logic in MoqGroupConsumer::new.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c15a63bf-ee0d-47a5-aa29-ad8d00d4131a

📥 Commits

Reviewing files that changed from the base of the PR and between ab9c96d and b30b6c1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • justfile
  • py/moq-lite/moq_lite/__init__.py
  • py/moq-lite/moq_lite/publish.py
  • py/moq-lite/moq_lite/subscribe.py
  • py/moq-lite/pyproject.toml
  • py/moq-lite/tests/test_local.py
  • rs/moq-ffi/src/consumer.rs
  • rs/moq-ffi/src/origin.rs
  • rs/moq-ffi/src/producer.rs
✅ Files skipped from review due to trivial changes (2)
  • py/moq-lite/pyproject.toml
  • py/moq-lite/moq_lite/init.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • rs/moq-ffi/src/origin.rs
  • py/moq-lite/moq_lite/subscribe.py

The ci and test recipes ran `uv run maturin develop` (which only syncs
the root workspace's dev group) and then `uv run --package moq-lite
--no-sync`, so pytest (declared in moq-lite's dev group) was never
installed. pyright failed with "Import \"pytest\" could not be resolved"
in py/moq-lite/tests/test_local.py.

Add an explicit `uv sync --package moq-lite` before the maturin step so
pytest is present; the subsequent maturin develop overrides the registry
moq-ffi with the source build, and --no-sync preserves it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated merged commit a20f85c into moq-dev:main Apr 17, 2026
1 check passed
@moq-bot moq-bot bot mentioned this pull request Apr 17, 2026
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