Skip to content

Conversation

@qzhuyan
Copy link
Collaborator

@qzhuyan qzhuyan commented Feb 3, 2026

  • TCP
  • TLS
  • QUIC
    with example code.

uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'CI' step
Uses Step
uses 'dtolnay/rust-toolchain' with ref 'stable', not a pinned commit hash
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Publish to PyPI' step
Uses Step
uses 'dtolnay/rust-toolchain' with ref 'stable', not a pinned commit hash
path: dist
merge-multiple: true

- uses: pypa/gh-action-pypi-publish@release/v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Publish to PyPI' step
Uses Step
uses 'pypa/gh-action-pypi-publish' with ref 'release/v1', not a pinned commit hash
@codecov-commenter
Copy link

codecov-commenter commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.88%. Comparing base (97939ab) to head (43f5d8e).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #84      +/-   ##
==========================================
- Coverage   74.89%   74.88%   -0.01%     
==========================================
  Files          63       63              
  Lines       15024    15024              
==========================================
- Hits        11252    11251       -1     
- Misses       3772     3773       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces Python FFI bindings for FlowSDK, providing both low-level FFI access and high-level async MQTT client capabilities. The implementation enables Python developers to use FlowSDK's MQTT functionality with support for multiple transport protocols (TCP, TLS, and QUIC).

Changes:

  • Added build tooling and CI/CD pipeline for Python bindings generation and PyPI publishing
  • Implemented high-level async MQTT client with support for TCP, TLS, and QUIC transports
  • Created comprehensive example suite demonstrating various usage patterns (async, select-based, low-level FFI)
  • Configured Python package structure with proper packaging metadata

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 35 comments.

Show a summary per file
File Description
scripts/build_python_bindings.sh Build script for generating Python bindings from Rust FFI using uniffi
python/package/setup.py Minimal setuptools configuration for package building
python/package/pyproject.toml Package metadata including version, dependencies, and build configuration
python/package/MANIFEST.in Specifies native library files to include in distribution
python/package/flowsdk/async_client.py High-level async MQTT client with protocol implementations for TCP/TLS/QUIC
python/package/flowsdk/__init__.py Package initialization exposing main API components
python/package/README.md User documentation with examples and API reference
python/examples/test_binding.py Low-level FFI binding verification tests
python/examples/simple_async_usage.py Minimal async client example
python/examples/select_example.py Non-blocking I/O example using select()
python/examples/asyncio_tcp_client_example.py TCP transport example with message handling
python/examples/asyncio_quic_client_example.py QUIC transport example
python/examples/async_tls_client_example.py TLS transport example
python/examples/async_example.py Manual asyncio integration with low-level API
python/examples/README.md Comprehensive examples documentation
.gitignore Excludes generated FFI files and Python artifacts
.github/workflows/release_pypi.yml PyPI publishing workflow for releases
.github/workflows/ci.yml Adds Python bindings build and test to CI pipeline

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +486 to +517
async def publish(self, topic: str, payload: bytes, qos: int = 0, retain: Optional[bool] = None) -> int:
"""
Publish a message.

Args:
topic: MQTT topic to publish to
payload: Message payload as bytes
qos: Quality of Service level (0, 1, or 2)
retain: Whether to retain the message (None uses default, ignored for QUIC)

Returns:
Packet ID of the publish (0 for QoS 0)

Raises:
RuntimeError: If not connected

Note:
QUIC transport does not support the retain parameter and priority.
"""
if not self.protocol:
raise RuntimeError("Not connected")

loop = asyncio.get_running_loop()

# Handle different engine publish signatures
if self.transport_type == TransportType.TCP:
# TCP engine takes 4 args: topic, payload, qos, priority
# Note: priority is currently passed as None as it's not exposed in high-level API yet
pid = self.engine.publish(topic, payload, qos, None)
else:
# TLS and QUIC engines take 3 args: topic, payload, qos
pid = self.engine.publish(topic, payload, qos)
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The retain parameter in the publish method is accepted but never used. Line 494 documents it as "None uses default, ignored for QUIC" but the parameter isn't passed to any engine's publish method (lines 514 and 517). This creates a misleading API where users might think they can control the retain flag, but it has no effect. Either implement retain flag support or remove it from the API.

Copilot uses AI. Check for mistakes.
print(f"✅ Published (PID: {ev[0].packet_id})")
elif ev.is_error():
print(f"❌ Error: {ev[0].message}")
elif ev.is_disconnected():
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Inconsistent comment style. Line 164 has a comment describing the disconnected event handling, but line 94 has the comment "Only TCP engine has handle_connection_lost" which seems more like implementation notes. The comment at line 164 should explain what happens when disconnected similar to the explanatory comments for other events, or the comment style should be consistent throughout.

Suggested change
elif ev.is_disconnected():
elif ev.is_disconnected():
# When the engine reports a disconnection, log the reason and
# exit the main loop so the cleanup in the finally block can run.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
except ImportError:
pass

try:
from .async_client import FlowMqttClient, FlowMqttProtocol, TransportType
except ImportError:
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except ImportError:
pass
try:
from .async_client import FlowMqttClient, FlowMqttProtocol, TransportType
except ImportError:
except ImportError:
# Optional FFI bindings: allow flowsdk to be imported even if flowsdk_ffi is unavailable.
pass
try:
from .async_client import FlowMqttClient, FlowMqttProtocol, TransportType
except ImportError:
# Optional async MQTT client: expose flowsdk package even when async_client cannot be imported.

Copilot uses AI. Check for mistakes.
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
pass
print("\n⏹ Interrupted by user, shutting down...", file=sys.stderr)

Copilot uses AI. Check for mistakes.
sent = sock.send(outgoing_data)
print(f"📤 Sent {sent} bytes")
outgoing_data = outgoing_data[sent:]
except (BlockingIOError, InterruptedError):
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
else:
print("📥 EOF from server")
return
except (BlockingIOError, InterruptedError):
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
print(f"📤 Publishing (PID: {pid})...")
published = True

except KeyboardInterrupt:
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except KeyboardInterrupt:
except KeyboardInterrupt:
# Allow user interruption (Ctrl+C) and proceed to the cleanup in the finally block.

Copilot uses AI. Check for mistakes.
./scripts/build_python_bindings.sh --release

- name: Build wheels
uses: pypa/cibuildwheel@v2

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Publish to PyPI' step
Uses Step
uses 'pypa/cibuildwheel' with ref 'v2', not a pinned commit hash
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