Client library for Sidekick Visual Coding Buddy
Project description
Sidekick Python Library (sidekick-py)
1. Overview
This library provides the Python interface ("Hero") for interacting with the Sidekick Visual Coding Buddy frontend UI. It allows Python scripts to easily create, update, interact with, and remove visual modules like grids, consoles, variable visualizers, drawing canvases, and UI controls within the Sidekick UI, typically via a mediating WebSocket Server.
The library abstracts the underlying WebSocket communication and JSON message formatting, offering an intuitive object-oriented API. It handles connection management, peer discovery, message buffering, event handling, and provides mechanisms for controlling the script's lifecycle and synchronizing with the Sidekick frontend state.
2. Key Features
- Object-Oriented API: Control visual modules (
Grid,Console,Viz,Canvas,Control) via Python classes and methods (using standard Pythonsnake_case). - Simplified Event Handling:
- Register specific callbacks (e.g.,
on_click,on_input_text) directly on module instances. - All modules provide an
on_error(callback)method.
- Register specific callbacks (e.g.,
- Automatic Connection Management:
- Lazy connection establishment (connects on first use).
- Manages WebSocket connection state (
DISCONNECTED,CONNECTING,CONNECTED_WAITING_SIDEKICK,CONNECTED_READY). - Handles keep-alive (Ping/Pong) via
websocket-client. - Runs a background listener thread (daemon) for incoming messages with timeouts for clean shutdown.
- Attempts graceful shutdown on normal script exit via
atexit.
- Peer Discovery & Status: Automatically announces itself (
system/announcewithrole: "hero") and listens for Sidekick (role: "sidekick") announcements to determine readiness (CONNECTED_READYstate). - Message Buffering: Automatically queues outgoing commands if Sidekick is not yet
CONNECTED_READY. The buffer is flushed when Sidekick becomes ready. - Configuration: Set WebSocket URL and automatic clearing behavior (
clear_on_connect,clear_on_disconnect). - Re-attachment Support: Module constructors support
spawn=Falseto attach to existing UI elements. - Reactive Visualization (
ObservableValue): A wrapper class to track changes in Python objects, enabling automatic, granular updates in theVizmodule. - Lifecycle Control:
run_forever(): Keeps the script running to process events indefinitely.shutdown(): Signalsrun_foreverto exit and cleans up the connection.
- Synchronization Primitives:
ensure_ready(): Blocks until Sidekick connection is established and ready.flush_messages(): Blocks until Sidekick is ready and all buffered messages have been sent.
- Global Message Handling: Option to register a handler to inspect all incoming messages.
3. Installation
Standard Installation (from PyPI):
pip install sidekick-py
Development Installation (from project root):
Install editable mode to link the installed package to your source code:
pip install -e libs/python
4. Core Concepts
4.1. Connection Management (connection.py)
- Singleton & State Machine: Manages a single, shared WebSocket connection using a state machine (
ConnectionStatus:DISCONNECTED,CONNECTING,CONNECTED_WAITING_SIDEKICK,CONNECTED_READY). Thread-safety is managed using anRLock. - Lazy Connection: The connection attempt is automatically triggered only when the first module is instantiated or when
sidekick.activate_connection()is explicitly called. - Configuration (
set_url,set_config): These global functions must be called before the first connection attempt is made.set_url(url: str): Sets the WebSocket server URL (defaults tows://localhost:5163).set_config(clear_on_connect: bool = True, clear_on_disconnect: bool = False):clear_on_connect: If True, sendsglobal/clearAllafter the connection becomesCONNECTED_READY.clear_on_disconnect: If True, attempts (best-effort) to sendglobal/clearAllduring theclose_connectionprocess.
- Listener Thread (
_listen_for_messages):- Runs as a background daemon thread to receive messages.
- Uses a timeout on
websocket.recv()to ensure it can periodically check for stop signals, allowing for clean shutdowns. - Handles
system/announcemessages to update peer status. - Dispatches incoming
eventanderrormessages to the appropriate module instance handler. - Calls the optional global message handler.
- Threading Events & Conditions: Uses
threading.Event(_stop_event,_ready_event,_shutdown_event) andthreading.Condition(_buffer_flushed_and_ready_condition) internally to coordinate thread startup, shutdown, readiness checks, and message flushing. - Shutdown Process (
close_connection,shutdown,atexit):close_connectionis the core cleanup function. It signals the listener thread to stop (_stop_event.set()), closes the WebSocket, and cleans up resources.shutdownsignalsrun_foreverto exit (_shutdown_event.set()) and then callsclose_connection.atexit.register(shutdown)ensures that on normal Python interpreter exit, theshutdownprocess is attempted, allowing the listener thread to terminate gracefully and the program to exit cleanly.
4.2. Peer Discovery & Message Buffering
- Announcements: On connection, Hero sends
system/announce(role: "hero", status: "online"). It listens forsystem/announcefrom Sidekick peers (role: "sidekick"). - Ready State: When the first Sidekick peer announces
online, the connection status transitions toCONNECTED_READY. The_ready_eventis set. - Buffering: Messages sent via module methods (e.g.,
grid.set_cell) before the status isCONNECTED_READYare queued in an internal buffer (_message_buffer). - Flushing: When the status becomes
CONNECTED_READY, the buffer is automatically flushed, sending queued messages. The_buffer_flushed_and_ready_conditionis notified when the buffer becomes empty while in the ready state.
4.3. Message Handling & Callbacks
- Dispatch: Incoming messages are first passed to the optional global handler. Messages with a
srcfield (indicating the source module instance) are then routed to the_internal_message_handlermethod of the corresponding Python module object (e.g., a specificGridinstance). - Internal Handler:
BaseModule._internal_message_handlerhandleserrormessages by calling theon_errorcallback. Module subclasses override this method to handle specificeventmessages (e.g.,type: "event", payload: {"event": "click", ...}) and invoke the relevant user callback (e.g.,on_click). - Specific Callbacks: Users interact with events primarily through methods like
grid.on_click(my_handler),console.on_input_text(my_handler), etc., abstracting the message parsing.
4.4. Module Interaction (BaseModule, spawn)
- Base Class:
BaseModuleprovides common functionality: ID management, connection activation, sending commands (_send_command,_send_update),remove()method, andon_errorregistration. - Instance ID (
instance_id): Uniquely identifies a module instance between Hero and Sidekick. Can be auto-generated or specified. spawn=True(Default): Creates a new visual instance in Sidekick by sending aspawncommand.spawn=False: Attaches the Python object to an existing visual instance in Sidekick (requiresinstance_id). Nospawncommand is sent.- Payloads: Internal methods ensure outgoing message payloads use
camelCasekeys as required by the protocol.
4.5. Reactivity (ObservableValue, Viz)
ObservableValue: A wrapper class for Python values. It intercepts common mutation methods (e.g.,append,__setitem__,add,set) and notifies subscribed callbacks with detailed change information.VizIntegration:viz.show(name, value): Displays a variable. Ifvalueis anObservableValue,Vizsubscribes to it._handle_observable_update: Internal callback inViztriggered byObservableValuechanges. It translates the change details into granularupdatemessages (e.g.,action: "setitem") for Sidekick, enabling efficient UI updates.
4.6. Lifecycle Control (run_forever, shutdown)
run_forever(): Call this if your script needs to stay alive to react to events (like button clicks or console input) after the main setup code has finished. It blocks the main thread untilshutdown()is called or Ctrl+C is pressed.shutdown(): Explicitly stops therun_foreverloop (if running) and initiates the connection closing process. Safe to call multiple times or even ifrun_foreverwasn't used. Automatically called on normal program exit viaatexit.
4.7. Synchronization (ensure_ready, flush_messages)
ensure_ready(timeout=None): Blocks execution until the connection has reached theCONNECTED_READYstate (meaning at least one Sidekick frontend is connected and has announced itself). Useful at the start of a script to wait for Sidekick before sending commands.flush_messages(timeout=None): Blocks execution until the connection isCONNECTED_READYand the internal outgoing message buffer is empty. Useful at the end of a short-lived script to increase the likelihood that Sidekick received all sent commands before the script exits (without needingtime.sleeporrun_forever).
5. API Reference
(Note: All methods sending messages construct payloads with camelCase keys as required by the protocol. Non-system messages are buffered until the connection is CONNECTED_READY.)
5.1. Top-Level Functions (sidekick namespace)
sidekick.set_url(url: str)- Sets the WebSocket Server URL (e.g.,
"ws://localhost:5163"). - Must be called before the first connection attempt.
- Sets the WebSocket Server URL (e.g.,
sidekick.set_config(clear_on_connect: bool = True, clear_on_disconnect: bool = False)- Configures automatic clearing behavior.
clear_on_connect: Sendsglobal/clearAllwhen Sidekick becomes ready.clear_on_disconnect: Attempts to sendglobal/clearAllon disconnect.- Must be called before the first connection attempt.
sidekick.activate_connection()- Ensures the connection attempt is initiated if currently disconnected.
- Called automatically by module constructors. Safe to call multiple times.
sidekick.clear_all()- Sends a
global/clearAllmessage to Sidekick (buffered if connection not ready).
- Sends a
sidekick.close_connection(log_info=True)- Manually initiates the closing of the WebSocket connection and cleanup. Consider using
shutdown()instead for consistency withatexitandrun_forever.
- Manually initiates the closing of the WebSocket connection and cleanup. Consider using
sidekick.shutdown()- Initiates the clean shutdown process: signals
run_foreverto exit (if running), stops the listener thread, and closes the WebSocket connection. - This is the recommended way to programmatically stop the connection. Automatically called by
atexit.
- Initiates the clean shutdown process: signals
sidekick.run_forever()- Blocks the main thread, keeping the script alive to process incoming events.
- Exits when
shutdown()is called or Ctrl+C is pressed.
sidekick.ensure_ready(timeout: Optional[float] = None) -> bool- Blocks until the connection status is
CONNECTED_READYor the timeout (in seconds) expires. - Returns
Trueif ready,Falseon timeout or disconnection.
- Blocks until the connection status is
sidekick.flush_messages(timeout: Optional[float] = None) -> bool- Blocks until the connection is
CONNECTED_READYand the outgoing message buffer is empty, or the timeout (in seconds) expires. - Returns
Trueif flushed while ready,Falseon timeout or disconnection.
- Blocks until the connection is
sidekick.register_global_message_handler(handler: Optional[Callable[[Dict[str, Any]], None]])- Registers or unregisters a single handler function that will be called with every message dictionary received from Sidekick. Use
Noneto unregister.
- Registers or unregisters a single handler function that will be called with every message dictionary received from Sidekick. Use
5.2. sidekick.ObservableValue
ObservableValue(initial_value: Any)- Wraps a Python value to enable change tracking.
- Methods:
.get() -> Any: Returns the current wrapped value..set(new_value: Any): Sets a new value, triggering a "set" notification..subscribe(callback: Callable[[Dict[str, Any]], None]) -> Callable[[], None]: Registers a callback for change notifications. Returns an unsubscribe function..unsubscribe(callback: Callable[[Dict[str, Any]], None]): Removes a specific callback.
- Intercepted Methods (trigger notifications):
.append(),.insert(),.pop(),.remove(),.clear(),.__setitem__(),.__delitem__(),.update()(for dicts),.add(),.discard()(for sets). - Other Dunder Methods: Delegates common methods like
__getattr__,__repr__,__str__,__eq__,__len__,__getitem__,__iter__,__contains__to the wrapped value.
5.3. sidekick.Grid
Grid(num_columns: int = 16, num_rows: int = 16, instance_id: Optional[str] = None, spawn: bool = True)- Represents a grid module.
spawn=Falserequiresinstance_id.
- Methods:
.set_cell(x: int, y: int, color: Optional[str] = None, text: Optional[str] = None): Sets cell state (columnx, rowy)..set_color(x: int, y: int, color: Optional[str]): Sets only the cell color..set_text(x: int, y: int, text: Optional[str]): Sets only the cell text..clear(): Clears the entire grid..remove(): Removes this grid instance from Sidekick.
- Event Handlers:
.on_click(callback: Optional[Callable[[int, int], None]]): Registers a function called withx,ywhen a cell is clicked. PassNoneto unregister..on_error(callback: Optional[Callable[[str], None]]): Registers a function called with an error message string specific to this instance. PassNoneto unregister.
5.4. sidekick.Console
Console(instance_id: Optional[str] = None, spawn: bool = True, initial_text: str = "", show_input: bool = False)- Represents a console module.
spawn=Falserequiresinstance_id;initial_textandshow_inputare ignored.
- Methods:
.print(*args: Any, sep: str = ' ', end: str = ''): Prints text to the console..log(message: Any): Shortcut for.print(message)..clear(): Clears the console text..remove(): Removes this console instance from Sidekick.
- Event Handlers:
.on_input_text(callback: Optional[Callable[[str], None]]): Registers a function called with the submitted text (requiresshow_input=True). PassNoneto unregister..on_error(callback: Optional[Callable[[str], None]]): Registers a function for instance-specific errors. PassNoneto unregister.
5.5. sidekick.Viz
Viz(instance_id: Optional[str] = None, spawn: bool = True)- Represents a variable visualizer module.
spawn=Falserequiresinstance_id.
- Methods:
.show(name: str, value: Any): Displays/updates a variable. Subscribes automatically ifvalueis anObservableValue..remove_variable(name: str): Removes a variable display. Unsubscribes if applicable..remove(): Removes this viz instance from Sidekick. Unsubscribes all tracked observables.
- Event Handlers:
.on_error(callback: Optional[Callable[[str], None]]): Registers a function for instance-specific errors. PassNoneto unregister. (Note: Viz currently doesn't emit user interaction events like 'click'.)
5.6. sidekick.Canvas
Canvas(width: int, height: int, instance_id: Optional[str] = None, spawn: bool = True, bg_color: Optional[str] = None)- Represents a 2D drawing canvas module.
spawn=Falserequiresinstance_id;width,height,bg_colorare ignored.
- Methods:
.clear(color: Optional[str] = None): Clears the canvas, optionally filling with a color..config(stroke_style: Optional[str] = None, fill_style: Optional[str] = None, line_width: Optional[int] = None): Configures drawing styles..draw_line(x1: int, y1: int, x2: int, y2: int): Draws a line..draw_rect(x: int, y: int, width: int, height: int, filled: bool = False): Draws a rectangle (outline or filled)..draw_circle(cx: int, cy: int, radius: int, filled: bool = False): Draws a circle (outline or filled)..remove(): Removes this canvas instance from Sidekick.
- Event Handlers:
.on_error(callback: Optional[Callable[[str], None]]): Registers a function for instance-specific errors. PassNoneto unregister. (Note: Canvas currently doesn't emit user interaction events.)
5.7. sidekick.Control
Control(instance_id: Optional[str] = None, spawn: bool = True)- Represents a UI control panel module.
spawn=Falserequiresinstance_id.
- Methods:
.add_button(control_id: str, text: str): Adds a button..add_text_input(control_id: str, placeholder: str = "", initial_value: str = "", button_text: str = "Submit"): Adds a text input field with a submit button..remove_control(control_id: str): Removes a specific button or text input by its ID..remove(): Removes this control panel instance from Sidekick.
- Event Handlers:
.on_click(callback: Optional[Callable[[str], None]]): Registers a function called with thecontrolIdwhen a button is clicked. PassNoneto unregister..on_input_text(callback: Optional[Callable[[str, str], None]]): Registers a function called withcontrolIdandvaluewhen a text input is submitted. PassNoneto unregister..on_error(callback: Optional[Callable[[str], None]]): Registers a function for instance-specific errors. PassNoneto unregister.
6. Development Notes
- Payload Keys: Remember that all keys in the
payloaddictionary sent to Sidekick MUST usecamelCase. This is handled internally by the library's methods. - Dependencies: Requires the
websocket-clientlibrary (pip install websocket-client). - Threading: The library uses a background daemon thread for receiving messages. Be mindful of thread safety if accessing shared state from module callbacks.
- Shutdown: Use
sidekick.shutdown()for explicit cleanup or rely onatexitfor normal program termination. Ensure long-running scripts usesidekick.run_forever()or manage their own main loop.
7. Troubleshooting
- Connection Errors:
- Check if the Sidekick WebSocket server is running (often part of the VS Code extension or run via
npm run devinwebapp). - Verify the URL (
ws://localhost:5163by default) usingsidekick.set_url()before creating modules. - Check firewalls.
- Enable DEBUG logging (
logging.getLogger("SidekickConn").setLevel(logging.DEBUG)) for detailed connection logs.
- Check if the Sidekick WebSocket server is running (often part of the VS Code extension or run via
- Module Commands Not Appearing:
- Check DEBUG logs. Are messages buffered? Did Sidekick become
CONNECTED_READY? Was the buffer flushed? - Inspect WebSocket messages in the Sidekick frontend (Browser DevTools > Network > WS). Verify message structure (
module,type,target) and ensurepayloadkeys arecamelCase. - Check the Sidekick browser console for errors processing the message.
- Check DEBUG logs. Are messages buffered? Did Sidekick become
- Callbacks Not Firing:
- Ensure the correct registration method was called on the module instance.
- Check DEBUG logs: Is the
eventorerrormessage being received from Sidekick? Does thesrcmatch theinstance_id? Does thepayload['event']match expectations? - Add logging inside your callback function. Check for exceptions within your callback (these are caught and logged by
SidekickConn).
- Script Doesn't Exit: Ensure you are not calling
sidekick.run_forever()unless intended. If using event callbacks that should keep the script alive, userun_forever()andshutdown(). Check for other non-daemon threads or blocking operations in your code. ensure_ready/flush_messagesTimeout: This usually means Sidekick did not connect or announce itself online within the timeout period. Check Sidekick server status and network connectivity. Increase the timeout if necessary for slow startups.VizNot Updating Reactively:- Ensure the value passed to
viz.show()is ansidekick.ObservableValue. - Mutations must happen through the
ObservableValuewrapper methods (e.g.,obs_list.append(item),obs_dict[key] = value,obs_value.set(new_val)).
- Ensure the value passed to
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sidekick_py-0.0.2.tar.gz.
File metadata
- Download URL: sidekick_py-0.0.2.tar.gz
- Upload date:
- Size: 41.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
061655a0b58d5308267381328c601c1c096893d27aebdfaa54370f77968b047f
|
|
| MD5 |
81473facb7b3bbd897e7633e9bd810ff
|
|
| BLAKE2b-256 |
4fce2bf0c3b2db9bd018f0fec5575f786bc17e54ae2b7481b80bdda7f8034d79
|
File details
Details for the file sidekick_py-0.0.2-py3-none-any.whl.
File metadata
- Download URL: sidekick_py-0.0.2-py3-none-any.whl
- Upload date:
- Size: 40.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22679e4d823d6dce33345dee60b8276420d2ecef0a718154ff0d67e0e7f2011b
|
|
| MD5 |
dbaa586b49c8ca8bf13aa6aa49331804
|
|
| BLAKE2b-256 |
0a00a126cd276d1a9dc7cbca1a0f2292d2cfac6dd888621cb454e35e8283c6ad
|