sidekick package

Sidekick Python Library (sidekick-py).

Welcome to the Sidekick Python library! This is your starting point for connecting your Python code to the Sidekick visual coding buddy, which typically runs inside the Visual Studio Code editor or as a standalone web application.

Sidekick helps you see your code come alive. Instead of just imagining what loops are doing or how data structures change, you can use this library to create visual representations and interactive elements directly within the Sidekick panel.

Key functionalities include creating interactive visual components, managing their layout, handling UI events, and visualizing data structures in real-time. The library is designed to be beginner-friendly with a synchronous-style API for CPython users, while also supporting asynchronous operations for more advanced use cases, especially in Pyodide environments.

Getting Started:

  1. Install: pip install sidekick-py.

  2. Setup Sidekick UI:

    • VS Code (Recommended): Install the “Sidekick - Your Visual Coding Buddy” extension from the VS Code Marketplace (search for sidekick-coding). Then, open the Sidekick panel using Ctrl+Shift+P (or Cmd+Shift+P on macOS) and searching for Sidekick: Show Panel.

    • Remote/Cloud: If not using the VS Code extension, your script might connect to a remote Sidekick server. If so, the library will print a UI URL to open in your browser.

  3. Import: Start your script with import sidekick.

  4. Create Components: E.g., label = sidekick.Label(“Hello!”). Component creation is non-blocking. The connection to a Sidekick service (local or remote) activates implicitly when the first component is created or an explicit connection function like sidekick.activate_connection() is called. activate_connection() itself is non-blocking.

  5. Wait for Connection (CPython, Optional but Recommended before interaction): If you need to ensure the connection is active before proceeding with operations that immediately require UI interaction in CPython, you can use sidekick.wait_for_connection(). This function will block until the connection is established or fails.

  6. Handle Interactivity: Use sidekick.run_forever() (for CPython) or await sidekick.run_forever_async() (for Pyodide/async) at the end of your script if you need to process UI events like button clicks. run_forever() and run_forever_async() will internally wait for the connection to be established before proceeding. Stop with Ctrl+C or by calling sidekick.shutdown() from a callback.

Happy visual coding!

sidekick.set_url(url: str | None) None[source]

Sets the target WebSocket URL for the Sidekick connection.

This URL will be used when the Sidekick connection is next activated. It must be called before any components are created or before activate_connection() or other connection-dependent functions are called.

If None is passed, Sidekick will revert to using its default server list (typically trying a local VS Code extension server first, then cloud fallbacks).

Parameters:

url (Optional[str]) – The WebSocket URL (e.g., “ws://custom.server/ws”) to connect to, or None to use default servers.

Raises:

ValueError – If the provided url is not None and is not a valid WebSocket URL string (i.e., does not start with “ws://” or “wss://”).

sidekick.activate_connection() None[source]

Ensures the Sidekick connection activation process is initiated.

This function is non-blocking. It schedules the asynchronous activation sequence if the service is not already active or in the process of activating. Component creation will also implicitly call this.

To synchronously wait for the connection to become fully active, use sidekick.wait_for_connection().

sidekick.wait_for_connection(timeout: float | None = None) None[source]

Blocks the calling thread until the Sidekick connection is fully active.

This function is primarily intended for CPython environments. It ensures that the connection to the Sidekick UI is established and ready before proceeding with operations that require an active connection.

Parameters:

timeout (Optional[float]) – The maximum time in seconds to wait. If None, a default timeout (_ACTIVATION_SYNC_WAIT_TIMEOUT_SECONDS) will be used.

Raises:
sidekick.clear_all() None[source]

Sends a command to remove all components from the Sidekick UI.

sidekick.register_global_message_handler(handler: Callable[[Dict[str, Any]], None] | None) None[source]

Registers a global handler for all incoming messages from the UI.

This is primarily for debugging or advanced use cases where you need to inspect every raw message received from the Sidekick UI.

Parameters:

handler (Optional[Callable[[Dict[str, Any]], None]]) – A function that accepts a single argument (the message dictionary). Pass None to remove a previously registered handler.

sidekick.run_forever() None[source]

Keeps the Python script running to handle UI events (for CPython).

This function first waits for the Sidekick connection to be established and then blocks the main thread, allowing Sidekick’s background event loop to run until a shutdown is requested (e.g., via sidekick.shutdown() in a callback or by pressing Ctrl+C).

async sidekick.run_forever_async() None[source]

Keeps the script running asynchronously to handle UI events.

This function is intended for Pyodide or asyncio-based applications. It asynchronously waits for the connection to be established and then for a shutdown signal.

sidekick.shutdown(wait: bool = False) None[source]

Initiates a clean shutdown of the Sidekick connection service.

This schedules the shutdown sequence in the event loop.

Parameters:

wait (bool) – If True (and in a CPython environment), this function will block until the service has fully shut down. Defaults to False.

sidekick.submit_interval(callback: Callable[[], Any], interval: float) Task[source]

Submits a function to be called repeatedly at a specified interval.

This is a convenient way to create animations or run periodic tasks without managing your own while True and time.sleep() loop. The task is automatically managed by Sidekick’s event loop and will be cleaned up when sidekick.shutdown() is called.

Parameters:
  • callback (Callable[[], Any]) – The function or coroutine to be executed at each interval. It should take no arguments.

  • interval (float) – The time in seconds to wait between each call. For example, 1/60 for 60 frames per second.

Returns:

The task object representing the interval execution. You

can use this to manually cancel the interval if needed.

Return type:

asyncio.Task

Raises:
  • ValueError – If interval is not a positive number.

  • TypeError – If callback_or_coro is not a callable function.

sidekick.submit_task(coro: Coroutine[Any, Any, Any]) Task[source]

Submits a user-defined coroutine to Sidekick’s managed event loop.

This function delegates directly to the TaskManager, allowing users to run their own asynchronous code concurrently with Sidekick’s operations.

Parameters:

coro (Coroutine[Any, Any, Any]) – The coroutine to execute.

Returns:

An asyncio.Task object representing the execution of the coroutine.

Return type:

asyncio.Task

sidekick.button module

Provides the Button class for creating clickable buttons in Sidekick.

Use the sidekick.Button class to add a standard clickable button to your Sidekick UI panel. Clicking the button in the UI triggers a callback function in your Python script, which receives a ButtonClickEvent object.

Buttons can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding them as children to a container’s constructor. You can also provide an instance_id to uniquely identify the button.

You can define the button’s click behavior in several ways:

  1. Using the on_click parameter in the constructor: button = sidekick.Button(“Run”, on_click=my_run_function, instance_id=”run-btn”)

  2. Using the button.on_click(callback) method after creation: button = sidekick.Button(“Submit”) button.on_click(handle_submission)

  3. Using the @button.click decorator: button = sidekick.Button(“Decorated”) @button.click def decorated_handler(event: sidekick.ButtonClickEvent): print(f”Button ‘{event.instance_id}’ clicked!”)

class sidekick.button.Button(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[ButtonClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a clickable Button component instance in the Sidekick UI.

Creates a button with a text label. Use the on_click method, the @button.click decorator, or the on_click constructor parameter to define what happens in Python when the button is clicked in the UI. The callback function will receive a ButtonClickEvent object.

instance_id

The unique identifier for this button instance.

Type:

str

text

The text label currently displayed on the button.

Type:

str

__init__(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[ButtonClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Button object and creates the UI element.

This function is called when you create a new Button, for example: my_button = sidekick.Button(“Click Here”, on_click=handle_my_click)

It sends a message to the Sidekick UI to display a new button.

Parameters:
  • text (str) – The initial text label displayed on the button.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this button. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this button should be placed. If None (the default), the button is added to the main Sidekick panel area.

  • on_click (Optional[Callable[[ButtonClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call when this button is clicked in the Sidekick UI. The function should accept one ButtonClickEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error related to this specific button occurs in the Sidekick UI. The function should take one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_click or on_error are provided but are not callable functions.

property text: str

The text label currently displayed on the button.

Setting this property updates the button’s text in the Sidekick UI. For example: my_button.text = “Submit Now”

Type:

str

on_click(callback: Callable[[ButtonClickEvent], None | Coroutine[Any, Any, None]] | None)[source]

Registers a function to be called when this button is clicked.

The provided callback function will be executed in your Python script when the user clicks this specific button in the Sidekick UI. The callback function will receive a ButtonClickEvent object, which contains the instance_id of this button and the event type (“click”).

You can also set this callback directly when creating the button using the on_click parameter in its constructor.

Parameters:

callback (Optional[Callable[[ButtonClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call on click. It should accept one ButtonClickEvent argument. The callback can be a regular function or a coroutine function (async def). Pass None to remove the current callback.

Raises:

TypeError – If callback is not a callable function or None.

Example

>>> from sidekick.events import ButtonClickEvent
>>>
>>> def my_action(event: ButtonClickEvent):
...     print(f"Button '{event.instance_id}' was pressed!")
...
>>> my_btn = sidekick.Button("Do Action", instance_id="action-button")
>>> my_btn.on_click(my_action)
>>> # sidekick.run_forever() # Needed to process clicks
click(func: Callable[[ButtonClickEvent], None | Coroutine[Any, Any, None]]) Callable[[ButtonClickEvent], None | Coroutine[Any, Any, None]][source]

Decorator to register a function to be called when this button is clicked.

This provides an alternative, more Pythonic way to set the click handler if you prefer decorators. The decorated function will receive a ButtonClickEvent object as its argument.

Parameters:

func (Callable[[ButtonClickEvent], Union[None, Coroutine[Any, Any, None]]]) – The function to register as the click handler. It should accept one ButtonClickEvent argument. The callback can be a regular function or a coroutine function (async def).

Returns:

The original function, allowing the decorator to be used directly.

Return type:

Callable[[ButtonClickEvent], Union[None, Coroutine[Any, Any, None]]]

Raises:

TypeError – If func is not a callable function.

Example

>>> from sidekick.events import ButtonClickEvent
>>>
>>> my_button = sidekick.Button("Run Me", instance_id="decorated-btn")
>>>
>>> @my_button.click
... def handle_button_press(event: ButtonClickEvent):
...     print(f"Button '{event.instance_id}' (decorated) was clicked!")
...     # Perform some action...
...
>>> # sidekick.run_forever() # Needed to process clicks

sidekick.canvas module

Provides the Canvas class for creating a 2D drawing surface in Sidekick.

Use the sidekick.Canvas class to create a blank rectangular area within the Sidekick panel where your Python script can draw simple graphics. This allows you to visually represent geometric concepts, create algorithm visualizations, build simple game graphics, or even produce basic animations controlled by your code.

The canvas can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding it as a child to a container’s constructor. You can also provide an instance_id to uniquely identify the canvas.

Key Features:

  • Drawing Primitives: Draw basic shapes like lines (draw_line), rectangles (draw_rect), circles (draw_circle), polygons (draw_polygon), ellipses (draw_ellipse), and text (draw_text).

  • Styling: Control the appearance with options for fill color (fill_color), line color (line_color), line width (line_width), and text size/color.

  • Coordinate System: The origin (0, 0) is at the top-left corner. The x-axis increases to the right, and the y-axis increases downwards. All units (coordinates, dimensions, radii) are in pixels.

  • Double Buffering: Create smooth, flicker-free animations using the canvas.buffer() context manager. This draws a complete frame off-screen before displaying it all at once.

  • Interactivity: Make your canvas respond to user clicks using the on_click() method or the on_click constructor parameter to register a callback function that receives a CanvasClickEvent object.

Basic Usage:
>>> import sidekick
>>> # Create a 300 pixel wide, 200 pixel tall canvas
>>> canvas = sidekick.Canvas(300, 200, instance_id="main-drawing-area")
>>> canvas.draw_line(10, 10, 290, 190, line_color='red')
Interactive Usage with a Parent Container:
>>> import sidekick
>>> from sidekick.events import CanvasClickEvent # Import the event type
>>>
>>> my_layout_row = sidekick.Row()
>>>
>>> def canvas_clicked(event: CanvasClickEvent):
...     print(f"Canvas '{event.instance_id}' clicked at ({event.x}, {event.y})")
...     # Assume canvas_in_row is accessible
...     canvas_in_row.draw_circle(event.x, event.y, 5, fill_color='green')
...
>>> canvas_in_row = sidekick.Canvas(
...     width=150, height=100,
...     parent=my_layout_row,
...     instance_id="interactive-canvas",
...     on_click=canvas_clicked
... )
>>> canvas_in_row.draw_circle(75, 50, 40, fill_color='blue')
>>> # sidekick.run_forever() # Keep script running to process clicks
class sidekick.canvas.Canvas(width: int, height: int, instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[CanvasClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a 2D drawing canvas component instance in the Sidekick UI.

Provides a surface within the Sidekick panel for programmatic drawing of shapes, text, and for creating simple animations. The canvas origin (0,0) is at the top-left corner. It can be nested within layout containers like Row or Column.

instance_id

The unique identifier for this canvas instance.

Type:

str

width

The width of the canvas drawing area in pixels (read-only).

Type:

int

height

The height of the canvas drawing area in pixels (read-only).

Type:

int

ONSCREEN_BUFFER_ID = 0
__init__(width: int, height: int, instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[CanvasClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes a new Canvas object and creates its UI element in Sidekick.

This function is called when you create a new Canvas, for example: my_drawing_area = sidekick.Canvas(400, 300, on_click=handle_drawing_click)

It sends a message to the Sidekick UI to display a new drawing canvas.

Parameters:
  • width (int) – The desired width of the canvas in pixels. Must be a positive integer (e.g., > 0).

  • height (int) – The desired height of the canvas in pixels. Must be a positive integer (e.g., > 0).

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this canvas. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this canvas should be placed. If None (the default), the canvas is added to the main Sidekick panel area.

  • on_click (Optional[Callable[[CanvasClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call when the user clicks on the canvas. The function should accept one CanvasClickEvent object as an argument, which contains instance_id, type, x (click x-coordinate), and y (click y-coordinate). The callback can be a regular function or a coroutine function (async def). Defaults to None.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error related to this specific canvas occurs in the Sidekick UI. The function should take one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If width or height are not positive integers, or if the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_click or on_error are provided but are not callable functions.

property width: int

The width of the canvas in pixels (read-only).

Set during initialization and cannot be changed later.

Type:

int

property height: int

The height of the canvas in pixels (read-only).

Set during initialization and cannot be changed later.

Type:

int

on_click(callback: Callable[[CanvasClickEvent], None | Coroutine[Any, Any, None]] | None)[source]

Registers a function to call when the user clicks on this canvas.

The provided callback function will be executed in your Python script. It will receive a CanvasClickEvent object containing the instance_id of this canvas, the event type (“click”), and the x and y coordinates of the click relative to the canvas’s top-left corner.

You can also set this callback directly when creating the canvas using the on_click parameter in its constructor.

Parameters:

callback (Optional[Callable[[CanvasClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call when the canvas is clicked. It must accept one CanvasClickEvent argument. The callback can be a regular function or a coroutine function (async def). Pass None to remove a previously registered callback.

Raises:

TypeError – If callback is not a callable function or None.

Example

>>> from sidekick.events import CanvasClickEvent
>>>
>>> def report_click(event: CanvasClickEvent):
...     print(f"Canvas '{event.instance_id}' clicked at ({event.x}, {event.y}).")
...     # my_drawing_area.draw_circle(event.x, event.y, 3, fill_color='red')
...
>>> my_drawing_area = sidekick.Canvas(200, 150, instance_id="click-zone")
>>> my_drawing_area.on_click(report_click)
>>> # sidekick.run_forever() # Needed to process clicks
click(func: Callable[[CanvasClickEvent], None | Coroutine[Any, Any, None]]) Callable[[CanvasClickEvent], None | Coroutine[Any, Any, None]][source]

Decorator to register a function to call when this canvas is clicked.

This provides an alternative, more Pythonic way to set the click handler if you prefer decorators. The decorated function will receive a CanvasClickEvent object as its argument.

Parameters:

func (Callable[[CanvasClickEvent], Union[None, Coroutine[Any, Any, None]]]) – The function to register as the click handler. It should accept one CanvasClickEvent argument. The callback can be a regular function or a coroutine function (async def).

Returns:

The original function, allowing the decorator to be used directly.

Return type:

Callable[[CanvasClickEvent], Union[None, Coroutine[Any, Any, None]]]

Raises:

TypeError – If func is not a callable function.

Example

>>> from sidekick.events import CanvasClickEvent
>>>
>>> interactive_canvas = sidekick.Canvas(100, 100, instance_id="decorator-canvas")
>>>
>>> @interactive_canvas.click
... def handle_canvas_interaction(event: CanvasClickEvent):
...     print(f"Canvas '{event.instance_id}' clicked at ({event.x}, {event.y}) via decorator!")
...     interactive_canvas.draw_rect(event.x - 2, event.y - 2, 4, 4, fill_color='green')
...
>>> # sidekick.run_forever() # Needed to process clicks
buffer() ContextManager[_CanvasBufferProxy, bool | None][source]

Provides a context manager (with statement) for efficient double buffering.

When you want to draw multiple shapes or create an animation frame, drawing each element directly to the screen can cause flickering. Double buffering solves this by first drawing everything to a hidden, off-screen buffer. Once all drawing operations for the frame are complete (when the with block ends), the entire content of the hidden buffer is instantly drawn to the visible canvas. This results in smoother graphics and animations.

Returns:

A context manager. When used in a with statement, it yields a _CanvasBufferProxy object. All drawing methods called on this proxy object will target the hidden buffer.

Return type:

ContextManager[_CanvasBufferProxy]

Example

>>> # Assume 'my_canvas' is an existing sidekick.Canvas instance
>>> with my_canvas.buffer() as frame_buffer:
...     # These draw calls happen on a hidden buffer
...     frame_buffer.clear() # Clear the hidden buffer first
...     frame_buffer.draw_circle(50, 50, 10, fill_color='red')
...     frame_buffer.draw_rect(100, 30, 20, 40, fill_color='blue')
...
>>> # When the 'with' block exits, the screen updates once with the red circle
>>> # and blue rectangle, avoiding flicker.
clear(buffer_id: int | None = None)[source]

Clears the specified canvas buffer (visible screen or an offscreen buffer).

If buffer_id is None or 0 (which is Canvas.ONSCREEN_BUFFER_ID), this will clear the main, visible canvas area in the Sidekick UI. If a positive buffer_id is provided (e.g., from within a canvas.buffer() context), it clears that specific offscreen buffer.

Parameters:

buffer_id (Optional[int]) – The ID of the buffer to clear. Defaults to None, which means the visible onscreen canvas (ID 0). For offscreen buffers obtained via canvas.buffer(), the proxy object automatically provides the correct ID.

Raises:

SidekickConnectionError – If sending the command to the UI fails.

draw_line(x1: int, y1: int, x2: int, y2: int, line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws a straight line segment between two points on the canvas.

Parameters:
  • x1 (int) – The x-coordinate of the starting point of the line.

  • y1 (int) – The y-coordinate of the starting point of the line.

  • x2 (int) – The x-coordinate of the ending point of the line.

  • y2 (int) – The y-coordinate of the ending point of the line.

  • line_color (Optional[str]) – The color of the line, as a CSS color string (e.g., ‘blue’, ‘#00FF00’). If None, the UI’s default line color will be used.

  • line_width (Optional[int]) – The thickness of the line in pixels. Must be a positive integer if provided. If None, the UI’s default line width will be used.

  • buffer_id (Optional[int]) – The ID of the buffer to draw on. Defaults to None (the visible onscreen canvas). When drawing inside a with canvas.buffer() as buf:, buf.draw_line(…) automatically targets the correct offscreen buffer.

Raises:
draw_rect(x: int, y: int, width: int, height: int, fill_color: str | None = None, line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws a rectangle on the canvas.

The (x, y) coordinates specify the top-left corner of the rectangle.

Parameters:
  • x (int) – The x-coordinate of the top-left corner.

  • y (int) – The y-coordinate of the top-left corner.

  • width (int) – The width of the rectangle in pixels. Must be non-negative.

  • height (int) – The height of the rectangle in pixels. Must be non-negative.

  • fill_color (Optional[str]) – The color to fill the rectangle with (CSS format). If None, the rectangle will not be filled (transparent fill).

  • line_color (Optional[str]) – The color of the rectangle’s outline (CSS format). If None, the UI’s default outline color is used.

  • line_width (Optional[int]) – The thickness of the outline in pixels. Must be non-negative if provided. A line_width of 0 typically means no outline. If None, the UI’s default outline width is used.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
  • ValueError – If width or height are negative, or if line_width is provided but is not a non-negative integer.

  • SidekickConnectionError – If sending command fails.

draw_circle(cx: int, cy: int, radius: int, fill_color: str | None = None, line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws a circle on the canvas.

Parameters:
  • cx (int) – The x-coordinate of the circle’s center.

  • cy (int) – The y-coordinate of the circle’s center.

  • radius (int) – The radius of the circle in pixels. Must be positive.

  • fill_color (Optional[str]) – Fill color (CSS format). No fill if None.

  • line_color (Optional[str]) – Outline color (CSS format). UI default if None.

  • line_width (Optional[int]) – Outline thickness (non-negative). UI default if None.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
draw_polyline(points: List[Tuple[int, int]], line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws a series of connected line segments (an open path) on the canvas.

Parameters:
  • points (List[Tuple[int, int]]) – A list of at least two (x,y) tuples representing the vertices of the polyline. For example: [(10, 10), (50, 50), (10, 90)] would draw a V-shape.

  • line_color (Optional[str]) – Color for all segments (CSS format). UI default if None.

  • line_width (Optional[int]) – Thickness for all segments (positive). UI default if None.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
  • ValueError – If points has fewer than 2 points, or if line_width is provided but is not a positive integer.

  • TypeError – If the points argument is not a list or if its elements are not valid (x,y) tuples/lists of numbers.

  • SidekickConnectionError – If sending command fails.

draw_polygon(points: List[Tuple[int, int]], fill_color: str | None = None, line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws a closed polygon shape on the canvas.

The last point in the points list will be automatically connected back to the first point to close the shape.

Parameters:
  • points (List[Tuple[int, int]]) – A list of at least three (x,y) tuples representing the vertices of the polygon.

  • fill_color (Optional[str]) – Fill color (CSS format). No fill if None.

  • line_color (Optional[str]) – Outline color (CSS format). UI default if None.

  • line_width (Optional[int]) – Outline thickness (non-negative). UI default if None.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
  • ValueError – If points has fewer than 3 points, or if line_width is provided but is not a non-negative integer.

  • TypeError – If the points argument is not a list or if its elements are not valid (x,y) tuples/lists of numbers.

  • SidekickConnectionError – If sending command fails.

draw_ellipse(cx: int, cy: int, radius_x: int, radius_y: int, fill_color: str | None = None, line_color: str | None = None, line_width: int | None = None, buffer_id: int | None = None)[source]

Draws an ellipse (or oval) shape on the canvas.

Parameters:
  • cx (int) – The x-coordinate of the ellipse’s center.

  • cy (int) – The y-coordinate of the ellipse’s center.

  • radius_x (int) – The horizontal radius of the ellipse in pixels. Must be positive.

  • radius_y (int) – The vertical radius of the ellipse in pixels. Must be positive.

  • fill_color (Optional[str]) – Fill color (CSS format). No fill if None.

  • line_color (Optional[str]) – Outline color (CSS format). UI default if None.

  • line_width (Optional[int]) – Outline thickness (non-negative). UI default if None.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
  • ValueError – If radius_x or radius_y are not positive, or if line_width is provided but is not a non-negative integer.

  • SidekickConnectionError – If sending command fails.

draw_text(x: int, y: int, text: str, text_color: str | None = None, text_size: int | None = None, buffer_id: int | None = None)[source]

Draws a string of text on the canvas.

The (x,y) coordinates typically define the starting point of the text’s baseline (for many fonts) or the top-left corner. The exact interpretation can depend on the UI’s rendering implementation.

Parameters:
  • x (int) – The x-coordinate for the text’s position.

  • y (int) – The y-coordinate for the text’s position.

  • text (str) – The text string to display.

  • text_color (Optional[str]) – The color of the text (CSS format). UI default if None.

  • text_size (Optional[int]) – The font size in pixels. Must be positive if provided. UI default if None.

  • buffer_id (Optional[int]) – Target buffer ID. Defaults to visible canvas.

Raises:
remove()[source]

Removes the canvas and its associated offscreen buffers from the Sidekick UI.

This method cleans up not only the visible canvas but also any offscreen buffers that were created for double buffering via canvas.buffer(). It’s important to call this when you’re done with a canvas to free up resources in the Sidekick UI.

sidekick.column module

Provides the Column class for vertically arranging components in Sidekick.

Use the sidekick.Column class to create a container that lays out its child components vertically, one below the other, from top to bottom.

Components are added to a Column in several ways:

  1. By passing the Column instance as the parent argument when creating the child component: my_col = sidekick.Column(instance_id=”main-layout”) label_in_col = sidekick.Label(“Title”, parent=my_col)

  2. By calling the column.add_child(component) method after both the Column and the child component have been created: my_input = sidekick.Textbox() my_col = sidekick.Column() my_col.add_child(my_input)

  3. By passing existing components directly to the Column constructor: label1 = sidekick.Label(“First”) button1 = sidekick.Button(“Submit”) my_col = sidekick.Column(label1, button1)

Columns themselves can be nested within other containers (like Row or another Column). The default top-level container in Sidekick also behaves like a Column. You can also provide an instance_id to uniquely identify the Column.

class sidekick.column.Column(*children: Component, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a Column layout container instance in the Sidekick UI.

Child components added to this container will be arranged vertically, one below the other.

instance_id

The unique identifier for this column instance.

Type:

str

__init__(*children: Component, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Column layout container and creates the UI element.

This function is called when you create a new Column, for example: my_column = sidekick.Column() or with initial children: label = sidekick.Label(“Name:”) my_column = sidekick.Column(label, parent=another_container, instance_id=”form-column”)

It sends a message to the Sidekick UI to display a new vertical layout container.

Parameters:
  • *children (Component) – Zero or more Sidekick component instances (e.g., Button, Label, another Column) to be immediately added as children to this column. These child components must already exist. When Column is created, it will attempt to move each of these children into itself.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this Column. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row) where this Column itself should be placed. If None (the default), the Column is added to the main Sidekick panel area (which acts like a root column).

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error message related to this specific Column container (not its children) is sent back from the Sidekick UI. The function should accept one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate, or if any of children are not valid Sidekick components.

  • TypeError – If parent is an invalid type, or if on_error is provided but is not a callable function.

add_child(child_component: Component)[source]

Moves an existing component into this Column container.

This method sends a command to the Sidekick UI instructing it to make the provided child_component a child of this Column. The child component will be visually placed inside the column layout, typically appended to the end of existing children (below the last child).

Note

The child_component must already exist (i.e., its __init__ must have been called). This method changes the parentage of an existing component, effectively moving it.

Parameters:

child_component (Component) – The Sidekick component instance (e.g., a Button, Textbox, Grid, another Column) to add as a child to this column.

Raises:
  • TypeError – If child_component is not a valid Sidekick component instance (i.e., not derived from Component).

  • SidekickConnectionError – If sending the update command to the UI fails.

Example

>>> col_container = sidekick.Column(instance_id="content-area")
>>> title_label = sidekick.Label("Settings") # Initially in root
>>> activate_button = sidekick.Button("Activate") # Initially in root
>>>
>>> # Move the label and button into the column
>>> col_container.add_child(title_label)
>>> col_container.add_child(activate_button)
>>> # Now title_label and activate_button appear vertically inside col_container

sidekick.component module

Provides the foundational Component class for all Sidekick visual components.

This module defines the Component class, which serves as the base for all visual elements in the Sidekick library (e.g., Grid, Button, Canvas). It encapsulates common functionalities required for any Python object that represents and interacts with a corresponding UI element in the Sidekick panel.

Key responsibilities managed by Component:

  • Unique Identification: Each component instance is assigned a unique instance_id. This ID is crucial for the Sidekick system to distinguish between different UI elements, especially when multiple components of the same type exist.

  • Command Sending: Components send commands (like “spawn” to create the UI element, or “update” to modify its state) to the Sidekick UI. This is done through internal helper methods that format messages according to the Sidekick protocol and delegate the sending to the central ConnectionService. The sending of the initial “spawn” command is non-blocking; the command is queued if the connection to the Sidekick service is not yet active.

  • Parenting and Layout: Components can be nested within layout containers (like Row or Column). The parent attribute, specified during initialization, determines where the component appears in the UI hierarchy.

  • Lifecycle Management: Components have a remove() method to instruct the Sidekick UI to destroy the visual element and to clean up associated resources on the Python side.

  • Error Handling: Users can register an on_error callback to be notified if the Sidekick UI reports an error related to a specific component instance. These errors are delivered as structured ErrorEvent objects.

  • Event Message Routing: Each component instance registers itself with the ConnectionService. When the UI sends back events (e.g., a button click) or error messages, the ConnectionService routes these messages to the _internal_message_handler of the correct Python Component object.

Note

While Component is fundamental, users of the Sidekick library will typically not instantiate Component directly. Instead, they will create instances of its more specialized subclasses like sidekick.Grid, sidekick.Console, etc.

class sidekick.component.Component(component_type: str, payload: Dict[str, Any] | None = None, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: object

Base class for all Sidekick component interface classes.

This class manages the fundamental aspects of a Sidekick component, including its unique identification, its relationship with parent containers, sending commands to the UI (via the central ConnectionService), and routing incoming messages (events or errors) from the UI back to the component.

component_type

A string identifying the type of Sidekick component (e.g., “grid”, “button”), as defined in the communication protocol.

Type:

str

instance_id

The unique identifier for this component instance. This ID is used in protocol messages to target this specific component in the Sidekick UI.

Type:

str

__init__(component_type: str, payload: Dict[str, Any] | None = None, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the base component and sends a “spawn” command to the UI.

This constructor is called by subclasses (e.g., Grid(), Button()). It performs several critical setup steps:

  1. ID Assignment: A unique instance_id is determined.

  2. Handler Registration: Registers an internal message handler with the ConnectionService for this component’s instance_id.

  3. Payload Preparation: Assembles initial configuration and parent information into a “spawn” message payload.

  4. Spawn Command Scheduling: Sends a “spawn” command to the Sidekick UI via the ConnectionService. This operation is non-blocking. The command is queued if the Sidekick service is not yet active. The implicit activation of the ConnectionService (if this is the first component) is also non-blocking.

  5. Error Callback Registration: Registers the on_error callback if provided.

Parameters:
  • component_type (str) – The internal type name of the component, matching the component field in the Sidekick protocol (e.g., “grid”, “button”).

  • payload (Optional[Dict[str, Any]]) – A dictionary containing initial configuration data specific to the component type. This data is sent to the UI as part of the “spawn” command.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this component. If None, an ID will be auto-generated. If provided, it must be unique across all Sidekick components in the current session.

  • parent (Optional[Union['Component', str]]) – The parent container component (e.g., a sidekick.Row or sidekick.Column instance) or its string instance_id. This determines where the new component is placed in the UI layout. If None, the component is added to the default top-level area of the Sidekick panel.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – An optional callback function. If the Sidekick UI sends an error message related to this component, this function will be called with an ErrorEvent object. The callback can be a regular function or a coroutine function (async def).

Raises:
  • ValueError – If the provided instance_id is invalid (e.g., empty) or if the ConnectionService detects a duplicate instance_id.

  • TypeError – If parent is not a Component instance, a string ID, or None, or if on_error is provided but is not a callable function.

  • SidekickDisconnectedError – If send_message is called (internally by _send_command) when the service is in a state where messages cannot be queued or sent (e.g., FAILED, SHUTTING_DOWN).

  • SidekickConnectionError – While direct connection errors are less likely to be raised by __init__ itself due to its non-blocking nature, subsequent operations or synchronous wait points like sidekick.wait_for_connection() may raise this if the underlying asynchronous activation fails.

on_error(callback: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None) None[source]

Registers a function to handle UI error messages for this component.

If the Sidekick UI encounters an error related to this component instance, it may send an “error” message back. This method defines a Python function to be called when such an error is received.

The callback receives an ErrorEvent object with instance_id, type (“error”), and a message string.

Parameters:

callback (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call upon receiving an error. It must accept one ErrorEvent argument. Can be a regular function or a coroutine function (async def). Pass None to clear a previously registered callback.

Raises:

TypeError – If callback is provided but is not a callable function or None.

remove() None[source]

Removes this component from the Sidekick UI and cleans up local resources.

Sends a “remove” command to the UI and unregisters the component’s message handler and callbacks on the Python side. After calling remove(), this component instance should generally not be used further, as it will no longer be synchronized with the UI and cannot receive events.

__del__() None[source]

Fallback attempt to unregister the message handler upon garbage collection.

Warning

It is strongly recommended to explicitly call the component.remove() method when a Sidekick component is no longer needed. Relying on __del__ for cleanup in Python has several caveats:

  • The timing of __del__ execution is not guaranteed and can be unpredictable.

  • __del__ might not be called at all if the object is part of a reference cycle that the garbage collector cannot break.

  • Errors raised within __del__ are often ignored and can be difficult to debug.

  • During interpreter shutdown, the state of modules and global variables (like those in sidekick_connection_module) can be unreliable.

This __del__ method provides a best-effort attempt to unregister the component’s message handler from the ConnectionService if remove() was not explicitly called. This is a defensive measure to reduce potential issues but should not be the primary cleanup mechanism.

sidekick.config module

Configuration for Sidekick server connections.

This module defines the structure for server configurations and provides a default list of servers that the Sidekick Python library will attempt to connect to. It also manages any user-defined URL set via sidekick.set_url().

The primary components are:

  • ServerConfig: A data class holding details for a single server endpoint, including its WebSocket URL, an optional UI URL (for remote servers), and flags indicating if a session ID is needed and if the UI URL should be shown.

  • DEFAULT_SERVERS: A list of ServerConfig instances. The library will iterate through this list, attempting to connect, starting with local options and falling back to remote ones if specified.

  • Functions to manage a user-specified URL, which, if set, overrides the DEFAULT_SERVERS list.

class sidekick.config.ServerConfig(name: str, ws_url: str, ui_url: str | None = None, requires_session_id: bool = False, show_ui_url: bool = False)[source]

Bases: object

Data class representing the configuration for a single Sidekick server.

name

A human-readable name for the server (e.g., “Local VS Code”, “Sidekick Cloud”).

Type:

str

ws_url

The WebSocket URL for the Sidekick server (e.g., “ws://localhost:5163”).

Type:

str

ui_url

The base URL for the web-based UI, if applicable (e.g., “https://remote-sidekick-ui.com”). Used for remote servers where the UI is hosted separately. For local VS Code, this is typically None.

Type:

Optional[str]

requires_session_id

If True, a unique session ID will be generated and appended to both ws_url (as a query parameter ?session=) and ui_url (typically as a path segment, e.g., /session_id). Defaults to False.

Type:

bool

show_ui_url

If True and a connection to this server is successful, the (potentially session-specific) ui_url will be printed to the console, prompting the user to open it. Defaults to False.

Type:

bool

name: str
ws_url: str
ui_url: str | None = None
requires_session_id: bool = False
show_ui_url: bool = False
sidekick.config.get_user_set_url() str | None[source]

Retrieves the WebSocket URL explicitly set by the user.

If sidekick.set_url() has been called with a URL, this function returns that URL. Otherwise, it returns None, indicating that the default server list should be used.

Returns:

The user-set URL, or None if not set.

Return type:

Optional[str]

sidekick.config.set_user_url_globally(url: str | None) None[source]

Sets or clears the user-defined WebSocket URL.

This function is called internally by sidekick.set_url() to store the user’s preference.

Parameters:

url (Optional[str]) – The WebSocket URL to set (e.g., “ws://custom.server/ws”). If None, it clears any previously set user URL, reverting to the default server list behavior.

Raises:

ValueError – If the provided url is not None and is not a valid WebSocket URL format (i.e., does not start with “ws://” or “wss://”).

sidekick.connection module

Handles the Sidekick service connection and provides module-level API functions.

This module acts as the primary public-facing API for Python scripts to interact with the Sidekick service. It manages a singleton instance of the ConnectionService (from sidekick.connection_service) which orchestrates the actual communication and lifecycle management.

The public functions exposed here (set_url, activate_connection, shutdown, run_forever, etc.) are wrappers that delegate their operations to the singleton ConnectionService instance. This design pattern simplifies the user-facing API while centralizing complex connection logic, making the library easier to use.

sidekick.connection.set_url(url: str | None) None[source]

Sets the target WebSocket URL for the Sidekick connection.

This URL will be used when the Sidekick connection is next activated. It must be called before any components are created or before activate_connection() or other connection-dependent functions are called.

If None is passed, Sidekick will revert to using its default server list (typically trying a local VS Code extension server first, then cloud fallbacks).

Parameters:

url (Optional[str]) – The WebSocket URL (e.g., “ws://custom.server/ws”) to connect to, or None to use default servers.

Raises:

ValueError – If the provided url is not None and is not a valid WebSocket URL string (i.e., does not start with “ws://” or “wss://”).

sidekick.connection.activate_connection() None[source]

Ensures the Sidekick connection activation process is initiated.

This function is non-blocking. It schedules the asynchronous activation sequence if the service is not already active or in the process of activating. Component creation will also implicitly call this.

To synchronously wait for the connection to become fully active, use sidekick.wait_for_connection().

sidekick.connection.wait_for_connection(timeout: float | None = None) None[source]

Blocks the calling thread until the Sidekick connection is fully active.

This function is primarily intended for CPython environments. It ensures that the connection to the Sidekick UI is established and ready before proceeding with operations that require an active connection.

Parameters:

timeout (Optional[float]) – The maximum time in seconds to wait. If None, a default timeout (_ACTIVATION_SYNC_WAIT_TIMEOUT_SECONDS) will be used.

Raises:
sidekick.connection.send_message(message_dict: Dict[str, Any]) None[source]

Sends a message dictionary (Sidekick protocol) to the Sidekick UI.

This is an internal-facing function primarily used by Component methods. It schedules the message to be sent by the ConnectionService.

Parameters:

message_dict (Dict[str, Any]) – The Sidekick protocol message to send.

sidekick.connection.register_message_handler(instance_id: str, handler: Callable[[Dict[str, Any]], None]) None[source]

Registers a message handler for a specific component instance ID.

Used internally by Component subclasses to route incoming UI events.

Parameters:
  • instance_id (str) – The unique ID of the component instance.

  • handler (Callable[[Dict[str, Any]], None]) – The function to call when a message for this instance_id is received.

sidekick.connection.unregister_message_handler(instance_id: str) None[source]

Unregisters a message handler for a specific component instance ID.

Called internally when a component is removed.

Parameters:

instance_id (str) – The unique ID of the component instance.

sidekick.connection.clear_all() None[source]

Sends a command to remove all components from the Sidekick UI.

sidekick.connection.shutdown(wait: bool = False) None[source]

Initiates a clean shutdown of the Sidekick connection service.

This schedules the shutdown sequence in the event loop.

Parameters:

wait (bool) – If True (and in a CPython environment), this function will block until the service has fully shut down. Defaults to False.

sidekick.connection.run_forever() None[source]

Keeps the Python script running to handle UI events (for CPython).

This function first waits for the Sidekick connection to be established and then blocks the main thread, allowing Sidekick’s background event loop to run until a shutdown is requested (e.g., via sidekick.shutdown() in a callback or by pressing Ctrl+C).

async sidekick.connection.run_forever_async() None[source]

Keeps the script running asynchronously to handle UI events.

This function is intended for Pyodide or asyncio-based applications. It asynchronously waits for the connection to be established and then for a shutdown signal.

sidekick.connection.submit_interval(callback: Callable[[], Any], interval: float) Task[source]

Submits a function to be called repeatedly at a specified interval.

This is a convenient way to create animations or run periodic tasks without managing your own while True and time.sleep() loop. The task is automatically managed by Sidekick’s event loop and will be cleaned up when sidekick.shutdown() is called.

Parameters:
  • callback (Callable[[], Any]) – The function or coroutine to be executed at each interval. It should take no arguments.

  • interval (float) – The time in seconds to wait between each call. For example, 1/60 for 60 frames per second.

Returns:

The task object representing the interval execution. You

can use this to manually cancel the interval if needed.

Return type:

asyncio.Task

Raises:
  • ValueError – If interval is not a positive number.

  • TypeError – If callback_or_coro is not a callable function.

sidekick.connection.submit_task(coro: Coroutine[Any, Any, Any]) Task[source]

Submits a user-defined coroutine to Sidekick’s managed event loop.

This function delegates directly to the TaskManager, allowing users to run their own asynchronous code concurrently with Sidekick’s operations.

Parameters:

coro (Coroutine[Any, Any, Any]) – The coroutine to execute.

Returns:

An asyncio.Task object representing the execution of the coroutine.

Return type:

asyncio.Task

sidekick.connection.register_global_message_handler(handler: Callable[[Dict[str, Any]], None] | None) None[source]

Registers a global handler for all incoming messages from the UI.

This is primarily for debugging or advanced use cases where you need to inspect every raw message received from the Sidekick UI.

Parameters:

handler (Optional[Callable[[Dict[str, Any]], None]]) – A function that accepts a single argument (the message dictionary). Pass None to remove a previously registered handler.

sidekick.console module

Provides the Console class for displaying text output in Sidekick.

Use the sidekick.Console class to create a dedicated text area within the Sidekick panel. This acts like a separate terminal or output window specifically for your script, allowing you to display status messages, log information, or show results without cluttering the main VS Code terminal.

The console can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding it as a child to a container’s constructor. You can also provide an instance_id to uniquely identify the console.

Key Features:

  • Text Output: Use the print() method (similar to Python’s built-in print) to append text messages to the console area.

  • Optional Text Input: Configure the console (show_input=True) to include a text input field at the bottom. Users can type text into this field and submit it back to your running Python script.

  • Input Handling: Use the on_submit() method or the on_submit constructor parameter to register a callback function that gets executed (receiving a ConsoleSubmitEvent object) whenever the user submits text from the input field.

  • Clearing: Use the clear() method to remove all previously displayed text.

Basic Usage:
>>> import sidekick
>>> console = sidekick.Console(instance_id="main-log")
>>> console.print("Script starting...")
Interactive Usage with a Parent Container:
>>> import sidekick
>>> from sidekick.events import ConsoleSubmitEvent # Import the event type
>>>
>>> my_column = sidekick.Column()
>>>
>>> def handle_command(event: ConsoleSubmitEvent):
...     print(f"Console '{event.instance_id}' received: '{event.value}'")
...     # Assume 'console_in_col' is accessible
...     console_in_col.print(f"Processing: {event.value}")
...
>>> console_in_col = sidekick.Console(
...     show_input=True,
...     parent=my_column,
...     instance_id="command-console",
...     on_submit=handle_command
... )
>>> # sidekick.run_forever() # Keep script running to process input
class sidekick.console.Console(text: str = '', show_input: bool = False, instance_id: str | None = None, parent: Component | str | None = None, on_submit: Callable[[ConsoleSubmitEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a Console component instance in the Sidekick UI panel.

Creates a scrollable text area for displaying output and optionally an input field if show_input is set to True. Can be nested within layout containers.

instance_id

The unique identifier for this console instance.

Type:

str

__init__(text: str = '', show_input: bool = False, instance_id: str | None = None, parent: Component | str | None = None, on_submit: Callable[[ConsoleSubmitEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Console object and creates the UI element.

This function is called when you create a new Console, for example: log_area = sidekick.Console() or for interactive use: cmd_console = sidekick.Console(show_input=True, on_submit=process_cmd)

It sends a message to the Sidekick UI to display a new console area.

Parameters:
  • text (str) – Text to display in the console area immediately after it’s created. Defaults to an empty string.

  • show_input (bool) – If True, an input field will be shown at the bottom of the console, allowing users to type and submit text back to the Python script. Defaults to False.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this console. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this console should be placed. If None (the default), the console is added to the main Sidekick panel area.

  • on_submit (Optional[Callable[[ConsoleSubmitEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call when the user submits text from the input field (if show_input is True). The function should accept one ConsoleSubmitEvent object as an argument, which contains instance_id, type, and value (the submitted text). The callback can be a regular function or a coroutine function (async def). Defaults to None.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error related to this specific console occurs in the Sidekick UI. The function should take one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_submit or on_error are provided but are not callable functions.

on_submit(callback: Callable[[ConsoleSubmitEvent], None | Coroutine[Any, Any, None]] | None)[source]

Registers a function to call when the user submits text from the console’s input field.

This method is only relevant if the console was initialized with show_input=True. The provided callback function will be executed in your Python script. It will receive a ConsoleSubmitEvent object containing the instance_id of this console, the event type (“submit”), and the value (the submitted text string).

You can also set this callback directly when creating the console using the on_submit parameter in its constructor.

Parameters:

callback (Optional[Callable[[ConsoleSubmitEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call when text is submitted. It must accept one ConsoleSubmitEvent argument. The callback can be a regular function or a coroutine function (async def). Pass None to remove a previously registered callback.

Raises:

TypeError – If callback is not a callable function or None.

Example

>>> from sidekick.events import ConsoleSubmitEvent
>>>
>>> def my_command_handler(event: ConsoleSubmitEvent):
...     if event.value == "help":
...         interactive_console.print("Available commands: ...")
...     else:
...         interactive_console.print(f"Unknown command: {event.value}")
...
>>> interactive_console = sidekick.Console(show_input=True, instance_id="cmd-line")
>>> interactive_console.on_submit(my_command_handler)
>>> # sidekick.run_forever() # Needed to process input
submit(func: Callable[[ConsoleSubmitEvent], None | Coroutine[Any, Any, None]]) Callable[[ConsoleSubmitEvent], None | Coroutine[Any, Any, None]][source]

Decorator to register a function to call when the user submits text from the console.

This provides an alternative, more Pythonic syntax to on_submit() if you prefer decorators. The decorated function will receive a ConsoleSubmitEvent object as its argument.

Parameters:

func (Callable[[ConsoleSubmitEvent], Union[None, Coroutine[Any, Any, None]]]) – The function to register as the submit handler. It must accept one ConsoleSubmitEvent argument. The callback can be a regular function or a coroutine function (async def).

Returns:

The original function, allowing the decorator to be used directly.

Return type:

Callable[[ConsoleSubmitEvent], Union[None, Coroutine[Any, Any, None]]]

Raises:

TypeError – If func is not a callable function.

Example

>>> from sidekick.events import ConsoleSubmitEvent
>>>
>>> command_line = sidekick.Console(show_input=True, instance_id="decorated-console")
>>>
>>> @command_line.submit
... def execute_command(event: ConsoleSubmitEvent):
...     command_line.print(f"Executing: {event.value} (from '{event.instance_id}')!")
...
>>> # sidekick.run_forever() # Needed to process submissions
print(*args: Any, sep: str = ' ', end: str = '\n')[source]

Prints messages to this console instance in the Sidekick UI.

This works very much like Python’s built-in print() function. You can pass multiple arguments, and they will be converted to strings, joined by the sep string, and finally, the end string will be appended.

Parameters:
  • *args (Any) – One or more objects to print. They will be converted to their string representations.

  • sep (str) – The separator string to place between arguments if multiple are provided. Defaults to a single space (’ ‘).

  • end (str) – The string to append at the end of the printed output. Defaults to a newline character (’n’).

Raises:

SidekickConnectionError – If sending the print command to the UI fails.

Example

>>> log_console = sidekick.Console(instance_id="app-log")
>>> item_id = 123
>>> status_code = 200
>>> log_console.print("Processing item:", item_id, "Status:", status_code)
clear()[source]

Removes all previously printed text from this console instance.

This will make the console area empty again.

Raises:

SidekickConnectionError – If sending the clear command to the UI fails.

sidekick.events module

Defines structured event objects for Sidekick Python library callbacks.

This module uses Python’s dataclasses to create clear, type-hinted event objects that are passed to user-defined callback functions registered with Sidekick components (e.g., on_click, on_submit, on_error).

Using structured event objects provides several benefits:

  • API Consistency: All event callbacks receive a single event object argument.

  • Type Safety: Clear type hints improve code understanding and help static analysis.

  • Extensibility: Adding new information to events in the future can be done by adding fields to these dataclasses without breaking existing callback signatures.

  • Rich Context: Event objects can carry common contextual information like the ID of the component instance that triggered the event and the type of the event, in addition to event-specific data.

class sidekick.events.BaseSidekickEvent(instance_id: str, type: str)[source]

Bases: object

Base class for all Sidekick events.

instance_id

The unique identifier of the Python component instance that is associated with this event (e.g., the button that was clicked, or the grid that reported an error from the UI).

Type:

str

type

A string indicating the specific type of event (e.g., “click”, “submit”, “error”).

Type:

str

instance_id: str
type: str
class sidekick.events.ButtonClickEvent(instance_id: str, type: str)[source]

Bases: BaseSidekickEvent

Event dispatched when a sidekick.Button is clicked in the UI.

instance_id

The ID of the Button instance that was clicked.

Type:

str

type

Always “click” for this event type.

Type:

str

class sidekick.events.GridClickEvent(instance_id: str, type: str, x: int, y: int)[source]

Bases: BaseSidekickEvent

Event dispatched when a cell in a sidekick.Grid is clicked in the UI.

instance_id

The ID of the Grid instance where the click occurred.

Type:

str

type

Always “click” for this event type.

Type:

str

x

The 0-based column index of the clicked cell.

Type:

int

y

The 0-based row index of the clicked cell.

Type:

int

x: int
y: int
class sidekick.events.CanvasClickEvent(instance_id: str, type: str, x: int, y: int)[source]

Bases: BaseSidekickEvent

Event dispatched when a sidekick.Canvas is clicked in the UI.

instance_id

The ID of the Canvas instance that was clicked.

Type:

str

type

Always “click” for this event type.

Type:

str

x

The x-coordinate of the click relative to the canvas’s top-left origin.

Type:

int

y

The y-coordinate of the click relative to the canvas’s top-left origin.

Type:

int

x: int
y: int
class sidekick.events.TextboxSubmitEvent(instance_id: str, type: str, value: str)[source]

Bases: BaseSidekickEvent

Event dispatched when text is submitted from a sidekick.Textbox in the UI (e.g., by pressing Enter or on blur).

instance_id

The ID of the Textbox instance from which text was submitted.

Type:

str

type

Always “submit” for this event type.

Type:

str

value

The text string that was submitted by the user.

Type:

str

value: str
class sidekick.events.ConsoleSubmitEvent(instance_id: str, type: str, value: str)[source]

Bases: BaseSidekickEvent

Event dispatched when text is submitted from a sidekick.Console’s input field (if show_input=True) in the UI.

instance_id

The ID of the Console instance from which text was submitted.

Type:

str

type

Always “submit” for this event type.

Type:

str

value

The text string that was submitted by the user.

Type:

str

value: str
class sidekick.events.ErrorEvent(instance_id: str, type: str, message: str)[source]

Bases: BaseSidekickEvent

Event dispatched when an error related to a specific Sidekick component is reported back from the Sidekick UI.

instance_id

The ID of the component instance that encountered or reported the error in the UI.

Type:

str

type

Always “error” for this event type.

Type:

str

message

A string describing the error that occurred.

Type:

str

message: str

sidekick.exceptions module

Custom application-level exceptions for the Sidekick Python library.

This module defines specific error types that users of the Sidekick library might encounter, particularly those related to establishing and maintaining a connection to the Sidekick service (which includes the UI panel and its communication layer).

These exceptions provide more context than generic Python errors and can help users understand and potentially handle different failure scenarios when interacting with Sidekick. They may wrap or be triggered by lower-level exceptions from the sidekick.core package.

exception sidekick.exceptions.SidekickError[source]

Bases: Exception

Base class for all application-level errors specific to the Sidekick library.

Catching this exception can be a way to handle any error explicitly raised by the Sidekick library itself, distinguishing it from general Python errors or errors from other libraries.

exception sidekick.exceptions.SidekickConnectionError(message: str, original_exception: BaseException | None = None)[source]

Bases: SidekickError

Base error for all Sidekick connection-related problems at the application level.

Catch this exception type if you want to handle any issue related to establishing or maintaining the connection to the full Sidekick service, including communication with the UI panel.

Example

>>> import sidekick
>>> try:
...     console = sidekick.Console() # Connection to Sidekick service happens here
...     console.print("Connected to Sidekick!")
... except sidekick.SidekickConnectionError as e:
...     print(f"Could not use Sidekick: {e}")
__str__() str[source]

Provide a more informative string representation.

exception sidekick.exceptions.SidekickConnectionRefusedError(message: str, url: str | None = None, original_exception: BaseException | None = None)[source]

Bases: SidekickConnectionError

Raised when the library fails to connect to the Sidekick service.

This usually means the Sidekick WebSocket server (typically run by the VS Code extension) wasn’t running or couldn’t be reached at the configured URL (e.g., “ws://localhost:5163” by default), or the underlying connection attempt was actively refused.

Common Causes: 1. The Sidekick panel isn’t open and active in VS Code. 2. The Sidekick VS Code extension isn’t running correctly or has

encountered an error starting its server.

  1. The WebSocket server couldn’t start (e.g., the port is already in use). Check VS Code’s “Sidekick Server” output channel for details.

  2. A firewall is blocking the connection.

  3. An incorrect URL was configured via sidekick.set_url().

url

The WebSocket URL that the connection attempt was made to, if available.

Type:

Optional[str]

exception sidekick.exceptions.SidekickTimeoutError(message: str, timeout_seconds: float | None = None, original_exception: BaseException | None = None)[source]

Bases: SidekickConnectionError

Raised when a Sidekick operation times out at the application level.

A common scenario for this error is when the connection to the Sidekick server (WebSocket) succeeds, but the Sidekick UI panel itself doesn’t respond by announcing its readiness (e.g., via a “system/announce” message) within an expected timeframe.

Common Causes: 1. The Sidekick panel is open in VS Code, but its web content (HTML/JS)

hasn’t finished loading or initializing (e.g., due to slow system performance or an internal UI error).

  1. An error within the Sidekick UI panel’s JavaScript code is preventing it from signaling readiness. Check the Webview Developer Tools in VS Code (Command Palette -> “Developer: Open Webview Developer Tools”) for errors.

timeout_seconds

The duration of the timeout in seconds, if specified.

Type:

Optional[float]

exception sidekick.exceptions.SidekickDisconnectedError(message: str = 'Connection to the Sidekick service was lost.', reason: str | None = None, original_exception: BaseException | None = None)[source]

Bases: SidekickConnectionError

Raised when the connection to the Sidekick service is lost after it was successfully established and active.

This indicates that communication was previously working, but the connection broke unexpectedly. This can happen if you try to send a command or if the background communication layer detects the disconnection.

Common Causes: 1. The Sidekick panel was closed in VS Code while your script was running. 2. The Sidekick VS Code extension crashed, was disabled, or VS Code itself was closed. 3. A network interruption occurred (less common for local connections). 4. An unrecoverable internal error occurred in the communication channel.

Important: The library will not automatically try to reconnect if this error occurs.

reason

A short description of why the disconnection occurred or was detected, if available.

Type:

Optional[str]

sidekick.grid module

Provides the Grid class for creating interactive 2D grids in Sidekick.

Use the sidekick.Grid class to create a grid of rectangular cells (like a spreadsheet, checkerboard, or pixel display) within the Sidekick panel. You can programmatically control the background color and display text within each individual cell of the grid from your Python script.

The grid can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding it as a child to a container’s constructor. You can also provide an instance_id to uniquely identify the grid.

This component is particularly useful for:

  • Visualizing 2D Data: Representing game maps, matrices, or cellular automata states.

  • Simple Graphics: Creating pixel art or basic pattern displays.

  • Interactive Elements: Building simple interactive boards or simulations where the user can click on cells to trigger actions or provide input to your Python script (using on_click() or the on_click constructor parameter, where the callback receives a GridClickEvent object).

Coordinate System:

Methods like set_color, set_text, clear_cell, and the on_click callback use (x, y) coordinates to identify specific cells within the grid:

  • x represents the column index, starting from 0 for the leftmost column.

  • y represents the row index, starting from 0 for the topmost row.

So, (0, 0) is the top-left cell.

Basic Usage:
>>> import sidekick
>>> my_grid = sidekick.Grid(num_columns=4, num_rows=3, instance_id="main-board")
>>> my_grid.set_color(x=0, y=0, color='blue')
Interactive Usage with a Parent Container:
>>> import sidekick
>>> from sidekick.events import GridClickEvent # Import the event type
>>>
>>> my_row = sidekick.Row()
>>>
>>> def user_clicked_cell(event: GridClickEvent):
...     print(f"Grid '{event.instance_id}' clicked at ({event.x}, {event.y})")
...     # Assume grid_in_row is accessible or passed differently if needed
...     grid_in_row.set_text(event.x, event.y, "Clicked!")
...
>>> grid_in_row = sidekick.Grid(
...     num_columns=5,
...     num_rows=5,
...     parent=my_row,
...     instance_id="interactive-grid",
...     on_click=user_clicked_cell
... )
>>> # sidekick.run_forever() # Keep script running to process clicks
class sidekick.grid.Grid(num_columns: int, num_rows: int, instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[GridClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents an interactive Grid component instance in the Sidekick UI.

Instantiate this class to create a grid of cells. You can set cell colors, text, and respond to user clicks on individual cells. The grid can be nested within layout containers like Row or Column.

instance_id

The unique identifier for this grid instance.

Type:

str

num_columns

The number of columns this grid has (read-only).

Type:

int

num_rows

The number of rows this grid has (read-only).

Type:

int

__init__(num_columns: int, num_rows: int, instance_id: str | None = None, parent: Component | str | None = None, on_click: Callable[[GridClickEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Grid object and creates the UI element.

This function is called when you create a new Grid, for example: game_board = sidekick.Grid(10, 10, on_click=handle_board_click)

It sends a message to the Sidekick UI to display a new grid.

Parameters:
  • num_columns (int) – The number of columns the grid should have. Must be a positive integer (e.g., > 0).

  • num_rows (int) – The number of rows the grid should have. Must be a positive integer (e.g., > 0).

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this grid. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this grid should be placed. If None (the default), the grid is added to the main Sidekick panel area.

  • on_click (Optional[Callable[[GridClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call when the user clicks on any cell in this grid. The function should accept one GridClickEvent object as an argument, which contains instance_id, type, x (column index), and y (row index) of the clicked cell. The callback can be a regular function or a coroutine function (async def). Defaults to None.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error related to this specific grid occurs in the Sidekick UI. The function should take one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If num_columns or num_rows are not positive integers, or if the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_click or on_error are provided but are not callable functions.

property num_columns: int

The number of columns in the grid (read-only).

This value is set when the grid is created and cannot be changed afterwards.

Type:

int

property num_rows: int

The number of rows in the grid (read-only).

This value is set when the grid is created and cannot be changed afterwards.

Type:

int

on_click(callback: Callable[[GridClickEvent], None | Coroutine[Any, Any, None]] | None)[source]

Registers a function to call when the user clicks on any cell in this grid.

The provided callback function will be executed in your Python script. It will receive a GridClickEvent object containing the instance_id of this grid, the event type (“click”), and the x (column index) and y (row index) of the cell that was clicked. Coordinates are 0-indexed.

You can also set this callback directly when creating the grid using the on_click parameter in its constructor.

Parameters:

callback (Optional[Callable[[GridClickEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call when a cell is clicked. It must accept one GridClickEvent argument. The callback can be a regular function or a coroutine function (async def). Pass None to remove a previously registered callback.

Raises:

TypeError – If callback is not a callable function or None.

Example

>>> from sidekick.events import GridClickEvent
>>>
>>> def handle_grid_interaction(event: GridClickEvent):
...     print(f"Grid '{event.instance_id}' cell ({event.x}, {event.y}) was clicked.")
...     my_interactive_grid.set_color(event.x, event.y, "yellow")
...
>>> my_interactive_grid = sidekick.Grid(3, 3, instance_id="game-board")
>>> my_interactive_grid.on_click(handle_grid_interaction)
>>> # sidekick.run_forever() # Needed to process clicks
click(func: Callable[[GridClickEvent], None | Coroutine[Any, Any, None]]) Callable[[GridClickEvent], None | Coroutine[Any, Any, None]][source]

Decorator to register a function to call when a cell in this grid is clicked.

This provides an alternative, more Pythonic way to set the click handler if you prefer decorators. The decorated function will receive a GridClickEvent object as its argument.

Parameters:

func (Callable[[GridClickEvent], Union[None, Coroutine[Any, Any, None]]]) – The function to register as the click handler. It should accept one GridClickEvent argument. The callback can be a regular function or a coroutine function (async def).

Returns:

The original function, allowing the decorator to be used directly.

Return type:

Callable[[GridClickEvent], Union[None, Coroutine[Any, Any, None]]]

Raises:

TypeError – If func is not a callable function.

Example

>>> from sidekick.events import GridClickEvent
>>>
>>> my_board = sidekick.Grid(5, 5, instance_id="decorator-grid")
>>>
>>> @my_board.click
... def highlight_cell(event: GridClickEvent):
...     print(f"Grid '{event.instance_id}' cell ({event.x}, {event.y}) clicked via decorator!")
...     my_board.set_color(event.x, event.y, "magenta")
...
>>> # sidekick.run_forever() # Needed to process clicks
set_color(x: int, y: int, color: str | None)[source]

Sets the background color of a specific cell in the grid.

Parameters:
  • x (int) – The column index of the cell (0 to num_columns - 1).

  • y (int) – The row index of the cell (0 to num_rows - 1).

  • color (Optional[str]) – The desired background color for the cell, as a CSS color string (e.g., ‘red’, ‘#FF0000’, ‘rgb(0,255,0)’). If you pass None, the cell’s color will be reset to the default background color in the UI.

Raises:
set_text(x: int, y: int, text: str | None)[source]

Sets the text content displayed inside a specific cell.

Any existing text in the cell will be replaced.

Parameters:
  • x (int) – The column index of the cell (0 to num_columns - 1).

  • y (int) – The row index of the cell (0 to num_rows - 1).

  • text (Optional[str]) – The text string to display in the cell. If you pass None or an empty string “”, any existing text in the cell will be cleared.

Raises:
clear_cell(x: int, y: int)[source]

Clears both the background color and text content of a specific cell.

This resets the cell at the given (x, y) coordinates to its default appearance (default background color, no text).

Parameters:
  • x (int) – The column index of the cell (0 to num_columns - 1).

  • y (int) – The row index of the cell (0 to num_rows - 1).

Raises:
clear()[source]

Clears the entire grid, resetting all cells to their default state.

This will remove all text and custom background colors from every cell in the grid.

Raises:

SidekickConnectionError – If sending the command to the UI fails.

sidekick.label module

Provides the Label class for displaying static or dynamic text in Sidekick.

Use the sidekick.Label class to add a simple, non-interactive text label to your Sidekick UI panel. You can set the initial text during creation and update it later by setting the text property.

Labels are useful for displaying information, titles, or descriptions alongside other components. They can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding them as children to a container’s constructor. You can also provide an instance_id to uniquely identify the label.

class sidekick.label.Label(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a non-interactive text Label component instance in the Sidekick UI.

Creates a simple text display area. You can set the initial text when creating the label and update the displayed text later by setting the label.text property.

Example

status_label = sidekick.Label(“Status: Idle”, instance_id=”status-display”) status_label.text = “Status: Processing…”

instance_id

The unique identifier for this label instance.

Type:

str

text

The text currently displayed by the label.

Type:

str

__init__(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Label object and creates the UI element.

This function is called when you create a new Label, for example: title = sidekick.Label(“My Application Title”, instance_id=”main-title”)

It sends a message to the Sidekick UI to display a new text label.

Parameters:
  • text (str) – The initial text to display on the label.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this label. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this label should be placed. If None (the default), the label is added to the main Sidekick panel area.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error message related to this specific label occurs in the Sidekick UI. The function should accept one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_error is provided but is not a callable function.

property text: str

The text currently displayed by the label.

Setting this property updates the label’s text in the Sidekick UI. For example: my_label.text = “New information here”

Type:

str

sidekick.markdown module

Provides the Markdown class for rendering Markdown content in Sidekick.

Use the sidekick.Markdown class to display text formatted using Markdown syntax within your Sidekick UI panel. This allows for richer text presentation compared to a simple Label, including headings, lists, bold/italic text, code blocks, links, and potentially images (depending on the frontend implementation’s capabilities).

Markdown components can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding them as children to a container’s constructor. You can also provide an instance_id to uniquely identify the Markdown component.

class sidekick.markdown.Markdown(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a component that renders Markdown formatted text in the Sidekick UI.

Creates an area where Markdown text is rendered as formatted content. You can set the initial Markdown string when creating the component and update the displayed content later by setting the markdown.text property with a new Markdown string.

Example

md_display = sidekick.Markdown(”# TitlenSome *italic* text.”, instance_id=”doc-viewer”) md_display.text += “n- A list item”

instance_id

The unique identifier for this Markdown instance.

Type:

str

text

The current Markdown text string being rendered.

Type:

str

__init__(text: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Markdown object and creates the UI element.

This function is called when you create a new Markdown component, for example: notes = sidekick.Markdown(“## Meeting Notesn- Discuss Xn- Review Y”, instance_id=”meeting-notes”)

It sends a message to the Sidekick UI to display a new area for rendering Markdown content.

Parameters:
  • text (str) – The initial Markdown text to render.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this Markdown component. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this Markdown component should be placed. If None (the default), it’s added to the main Sidekick panel area.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error message related to this specific Markdown component occurs in the Sidekick UI. The function should accept one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_error is provided but is not a callable function.

property text: str

The current Markdown text being rendered.

Setting this property updates the rendered content in the Sidekick UI by providing a new Markdown string. For example: my_markdown.text = “### New SectionnDetails here.”

Type:

str

sidekick.observable_value module

Provides the ObservableValue class for making Sidekick visualizations reactive.

This module contains the ObservableValue class, a special wrapper you can use around your regular Python lists, dictionaries, or sets.

Why use it?

The main purpose of ObservableValue is to work hand-in-hand with the sidekick.Viz component. When you display an ObservableValue using viz.show(), the Viz panel in Sidekick gains a superpower: it automatically updates its display whenever you modify the data inside the ObservableValue.

How it works:

  1. Wrap your data: my_list = sidekick.ObservableValue([1, 2])

  2. Show it in Viz: viz.show(“My List”, my_list)

  3. Modify the data using the wrapper’s methods:

    • my_list.append(3)

    • my_list[0] = 99

    • del my_list[1]

  4. Observe: The Viz panel in Sidekick updates instantly to show these changes, often highlighting exactly what was modified.

This makes it much easier to track how your data structures evolve during your script’s execution without needing to manually call viz.show() after every single change.

Note on Limitations:

  • Automatic updates only occur when you modify the data through the ObservableValue wrapper’s methods (like .append(), [key]=value, .add()). Changes made directly to the underlying object obtained via .get() might not be detected automatically.

  • For nested structures (e.g., a list inside a dictionary), you would need to wrap the inner mutable structures with ObservableValue as well if you want their internal changes to trigger automatic updates.

  • Changes made by directly setting attributes on a wrapped custom object are generally not detected automatically.

class sidekick.observable_value.ObservableValue(value: Any)[source]

Bases: object

Wraps a Python value (list, dict, set) to notify subscribers about changes.

Use this class to make your data “reactive” when displayed using the sidekick.Viz component. By wrapping mutable data structures (lists, dictionaries, sets) in an ObservableValue, you enable the Viz panel in Sidekick to update its display automatically whenever you modify the wrapped data through this wrapper object.

This is achieved by intercepting common modification methods (like append, __setitem__, update, add, clear, etc.) and sending notifications after the operation completes.

Basic Usage:
>>> import sidekick
>>> viz = sidekick.Viz() # Assuming Viz panel is ready
>>>
>>> # Wrap a list
>>> shopping_list = sidekick.ObservableValue(['apples', 'bananas'])
>>> viz.show("Groceries", shopping_list)
>>>
>>> # Modify through the wrapper - Viz updates automatically!
>>> shopping_list.append('carrots')
>>> shopping_list[0] = 'blueberries'
>>>
>>> # Wrap a dictionary
>>> config = sidekick.ObservableValue({'theme': 'dark', 'autosave': False})
>>> viz.show("Settings", config)
>>>
>>> # Modify through the wrapper - Viz updates automatically!
>>> config['autosave'] = True
>>> config.update({'fontSize': 12})
>>> del config['theme']

Key Benefit: Simplifies tracking data changes visually in Sidekick, as you don’t need repeated calls to viz.show() after each modification to an observed value.

See the module docstring for important limitations regarding direct modification of unwrapped values or attributes of wrapped custom objects.

__init__(value: Any)[source]

Initializes the ObservableValue by wrapping the provided Python value.

Parameters:

value – The Python value (e.g., a list, dict, set, number, string, etc.) that you want to make observable.

subscribe(callback: Callable[[Dict[str, Any]], None]) Callable[[], None][source]

Registers a function to be called whenever the wrapped value changes. (Internal).

This method is primarily intended for internal use by the sidekick.Viz component. When viz.show() is called with an ObservableValue, Viz uses this method to register its own internal handler (_handle_observable_update).

When a change occurs to the wrapped value (triggered by methods like append(), __setitem__(), set(), etc.), the callback function provided here is executed with a dictionary containing details about that specific change (e.g., type of change, path, new value).

Parameters:

callback – A function that accepts one argument: a dictionary describing the change event (common keys include ‘type’, ‘path’, ‘value’, ‘key’, ‘old_value’, ‘length’).

Returns:

A function that, when called with no arguments,

will remove this specific callback from the subscription list. This allows Viz to clean up its listener when the variable is removed from the display or the Viz panel itself is removed.

Return type:

UnsubscribeFunction

Raises:

TypeError – If the provided callback is not a callable function.

unsubscribe(callback: Callable[[Dict[str, Any]], None])[source]

Removes a previously registered callback function. (Internal).

Typically called by the unsubscribe function returned from subscribe, or potentially directly by Viz during cleanup.

Parameters:

callback – The specific callback function instance to remove from the set of subscribers.

get() Any[source]

Returns the actual underlying Python value being wrapped by this ObservableValue.

Use this method when you need direct access to the original list, dictionary, set, or other object stored inside, without the ObservableValue wrapper’s notification logic.

Be cautious: Modifying mutable objects obtained via get() directly (e.g., my_obs_list.get().append(item)) will not trigger automatic notifications to subscribers like sidekick.Viz. For automatic updates, always modify through the ObservableValue wrapper itself (my_obs_list.append(item)).

Returns:

The actual Python object currently stored within the wrapper.

Example

>>> obs_list = sidekick.ObservableValue([10, 20, 30])
>>> raw_list = obs_list.get()
>>> print(raw_list)
[10, 20, 30]
>>> print(type(raw_list))
<class 'list'>
>>> # Modifying raw_list directly does NOT notify Viz
>>> raw_list.pop()
30
>>> # Modifying through the wrapper DOES notify Viz
>>> obs_list.pop() # Viz will update
set(new_value: Any)[source]

Replaces the currently wrapped value with a completely new value.

This method is used when you want to assign a fundamentally different object (e.g., a new list, a different dictionary, a number instead of a list) to this observable variable, rather than just modifying the contents of the existing wrapped object.

It triggers a “set” notification to all subscribers, indicating that the entire value has been replaced.

Note

If the new_value you provide is the exact same object in memory as the currently wrapped value (i.e., new_value is self.get()), this method will do nothing and send no notification, as the value hasn’t conceptually changed from the wrapper’s perspective.

Parameters:

new_value – The new Python object to wrap and observe going forward.

Example

>>> obs_data = sidekick.ObservableValue({"status": "pending"})
>>> viz.show("Job Status", obs_data) # Show initial state
>>>
>>> # Replace the entire dictionary
>>> final_status = {"status": "complete", "result": 123}
>>> obs_data.set(final_status) # Viz panel updates to show the new dict
>>>
>>> # Further modifications through obs_data now affect final_status
>>> obs_data["timestamp"] = time.time() # Viz updates again
append(item: Any)[source]

Appends an item to the end of the wrapped list/sequence and notifies subscribers.

This method mimics the behavior of list.append(). It requires the wrapped value (self.get()) to be a mutable sequence (like a standard Python list).

Raises:
  • AttributeError – If the wrapped value does not have an append method.

  • TypeError – If the wrapped value is not a list or similar mutable sequence (though usually caught by AttributeError first).

Example

>>> items = sidekick.ObservableValue(['a', 'b'])
>>> viz.show("Items", items)
>>> items.append('c') # Viz automatically updates to show ['a', 'b', 'c']
insert(index: int, item: Any)[source]

Inserts an item at a specific index in the wrapped list/sequence and notifies subscribers.

Mimics list.insert(). Requires the wrapped value to be a mutable sequence.

Parameters:
  • index (int) – The index at which to insert the item.

  • item (Any) – The item to insert.

Raises:
  • AttributeError – If the wrapped value does not have an insert method.

  • TypeError – If the wrapped value is not a list or similar.

  • IndexError – If the index is out of range for insertion (behavior matches list.insert).

pop(index: int = -1) Any[source]

Removes and returns the item at the given index (default last) and notifies subscribers.

Mimics list.pop(). Requires the wrapped value to be a mutable sequence.

Parameters:

index (int) – The index of the item to remove. Defaults to -1 (the last item).

Returns:

The item that was removed from the list.

Raises:
  • AttributeError – If the wrapped value does not have a pop method.

  • TypeError – If the wrapped value is not a list or similar.

  • IndexError – If the list is empty or the index is out of range.

remove(value: Any)[source]

Removes the first occurrence of a given value from the wrapped list/sequence and notifies subscribers.

Mimics list.remove(). Requires the wrapped value to be a mutable sequence. If the value is not found, it does nothing (and sends no notification), matching the standard list.remove() behavior.

Parameters:

value (Any) – The value to search for and remove the first instance of.

Raises:
  • AttributeError – If the wrapped value does not have a remove method.

  • TypeError – If the wrapped value is not a list or similar.

  • ValueError – While this method catches the ValueError from list.remove (when the value isn’t found) and does nothing, the underlying list.index call used for notification could raise it if the value disappears between the check and the call (highly unlikely).

clear()[source]

Removes all items from the wrapped container (list, dict, set) and notifies subscribers.

Requires the wrapped value to have a callable clear() method.

Raises:
  • AttributeError – If the wrapped object does not have a clear method.

  • TypeError – If the wrapped object’s clear attribute is not callable.

__setitem__(key: Any, value: Any)[source]

Sets the value for a key/index in the wrapped container (dict/list) and notifies subscribers.

This method intercepts the standard Python square bracket assignment syntax: my_observable[key] = value

It performs the assignment operation on the wrapped object (self._value) and then, if successful, triggers a “setitem” notification to inform subscribers (like Viz) about the change. This works for both dictionary key assignment (my_dict[key] = val) and list index assignment (my_list[index] = val).

Parameters:
  • key (Any) – The key (for dictionaries) or index (for lists) to assign to.

  • value (Any) – The new value to associate with the key/index.

Raises:
  • TypeError – If the wrapped value does not support item assignment (e.g., if it’s a set, tuple, or immutable object).

  • IndexError – If the wrapped value is a list and the index is out of range.

  • KeyError – Typically not raised by assignment itself, but underlying checks might.

__delitem__(key: Any)[source]

Deletes an item/key from the wrapped container (dict/list) and notifies subscribers.

Intercepts the standard Python del statement used with square brackets: del my_observable[key]

It performs the deletion operation on the wrapped object (self._value) and then, if successful, triggers a “delitem” notification. Works for deleting dictionary keys or list items by index.

Parameters:

key (Any) – The key (for dictionaries) or index (for lists) to delete.

Raises:
  • TypeError – If the wrapped value does not support item deletion.

  • KeyError – If the wrapped value is a dictionary and the key is not found.

  • IndexError – If the wrapped value is a list and the index is out of range.

update(other: Dict[Any, Any] | Mapping[Any, Any] | Iterable[Tuple[Any, Any]] = {}, **kwargs: Any)[source]

Updates the wrapped dictionary with key-value pairs from another mapping and/or keyword arguments, notifying subscribers for each change.

Mimics the behavior of dict.update(). It iterates through the items to be added or updated and uses the intercepted __setitem__ method for each one. This ensures that subscribers (like Viz) receive individual “setitem” notifications for every key that is added or whose value is changed, allowing for more granular UI updates compared to a single bulk update notification.

Requires the wrapped value (self.get()) to be a mutable mapping (like a standard Python dict).

Parameters:
  • other – Can be another dictionary, an object implementing the Mapping protocol, or an iterable of key-value pairs (like [(‘a’, 1), (‘b’, 2)]). Keys and values from other will be added/updated in the wrapped dict.

  • **kwargs – Keyword arguments are treated as additional key-value pairs to add or update in the wrapped dictionary.

Raises:
  • AttributeError – If the wrapped value does not behave like a dictionary (no __setitem__).

  • TypeError – If the wrapped value is not a dictionary or similar mutable mapping, or if the other argument is not a valid source for updates.

Example

>>> settings = sidekick.ObservableValue({'font': 'Arial', 'size': 10})
>>> viz.show("Settings", settings)
>>>
>>> # Update using another dictionary
>>> settings.update({'size': 12, 'theme': 'dark'}) # Sends 2 notifications
>>> # Update using keyword arguments
>>> settings.update(line_numbers=True, theme='light') # Sends 2 notifications (theme overwritten)
add(element: Any)[source]

Adds an element to the wrapped set and notifies subscribers if the element was not already present.

Mimics set.add(). Requires the wrapped value to be a mutable set. If the element is already in the set, this method does nothing (and sends no notification), matching standard set behavior.

Parameters:

element (Any) – The element to add to the set.

Raises:
  • AttributeError – If the wrapped value does not have an add method or is not a set.

  • TypeError – If the wrapped value is not a set or similar mutable set.

discard(element: Any)[source]

Removes an element from the wrapped set if it is present, and notifies subscribers if removal occurred.

Mimics set.discard(). Requires the wrapped value to be a mutable set. If the element is not found in the set, this method does nothing (and sends no notification), matching standard set behavior.

Parameters:

element (Any) – The element to remove from the set.

Raises:
  • AttributeError – If the wrapped value does not have a discard method.

  • TypeError – If the wrapped value is not a set or similar.

__getattr__(name: str) Any[source]

Delegates attribute access to the wrapped value if the attribute is not internal.

This allows you to conveniently access methods and attributes of the wrapped object directly through the ObservableValue instance. For example, if obs_list = ObservableValue([1, 2]), calling obs_list.count(1) will correctly delegate to the underlying list’s count method.

It prevents direct access to the ObservableValue’s own internal attributes (like _value, _subscribers) via this delegation mechanism to avoid conflicts.

Parameters:

name (str) – The name of the attribute being accessed.

Returns:

The value of the attribute from the wrapped object.

Return type:

Any

Raises:

AttributeError – If the attribute name refers to an internal attribute of ObservableValue itself, or if the wrapped object does not have an attribute with the given name.

__setattr__(name: str, value: Any)[source]

Sets internal attributes or delegates attribute setting to the wrapped value.

This method controls how attribute assignment works on the ObservableValue instance:

  • If the name being assigned matches one of the ObservableValue’s own internal attributes (defined in _obs_internal_attrs, e.g., _value), it sets that internal attribute directly on the wrapper instance itself.

  • Otherwise (if name is not an internal attribute), it attempts to set the attribute with the given name and value directly on the wrapped object (self._value).

Important: Delegating attribute setting to the wrapped object via this method does not automatically trigger notifications to subscribers like Viz. If you need the Viz panel to update when you change an attribute of a wrapped custom object, you have several options:

  1. Call .set(self.get()) on the ObservableValue after modifying the wrapped object’s attribute(s). This forces a full “set” notification, telling Viz to re-render the entire object display.

  2. If the attribute itself holds mutable data (like a list), consider wrapping that attribute’s value in its own ObservableValue.

  3. If the custom object has its own notification mechanism, trigger it manually.

Parameters:
  • name (str) – The name of the attribute to set.

  • value (Any) – The value to assign to the attribute.

Raises:

AttributeError – If attempting to set an attribute on a wrapped object that doesn’t support attribute assignment (e.g., built-in types like int or list, or objects without __slots__ or __dict__ allowing the assignment).

__repr__() str[source]

Returns a string representation showing it’s an ObservableValue wrapping another value.

Example: ObservableValue([1, 2, 3])

__str__() str[source]

Returns the string representation of the wrapped value.

Allows str(my_observable) to behave the same as str(my_observable.get()).

__eq__(other: Any) bool[source]

Compares the wrapped value for equality.

Allows comparing an ObservableValue directly with another value or another ObservableValue. The comparison is performed on the underlying wrapped values.

Example

>>> obs1 = ObservableValue([1, 2])
>>> obs2 = ObservableValue([1, 2])
>>> obs1 == [1, 2] # True
>>> obs1 == obs2   # True
__len__() int[source]

Returns the length of the wrapped value, if the wrapped value supports len().

Allows using len(my_observable) just like len(my_observable.get()).

Raises:

TypeError – If the wrapped object type does not have a defined length (e.g., numbers, None, objects without __len__).

__getitem__(key: Any) Any[source]

Allows accessing items/keys of the wrapped value using square bracket notation ([]).

Enables syntax like my_observable[key] or my_observable[index] if the wrapped object (self.get()) supports item access (like lists, dictionaries, or custom objects implementing __getitem__).

Parameters:

key (Any) – The key or index to access within the wrapped object.

Returns:

The value associated with the key/index in the wrapped object.

Return type:

Any

Raises:
  • TypeError – If the wrapped object does not support item access (__getitem__).

  • KeyError – If the wrapped object is a dictionary and the key is not found.

  • IndexError – If the wrapped object is a list and the index is out of range.

__iter__() Iterable[Any][source]

Allows iterating over the wrapped value (e.g., in a for loop).

Enables syntax like for item in my_observable: if the wrapped object (self.get()) is itself iterable (like lists, dictionaries, sets, strings, or custom objects implementing __iter__).

Yields:

The items produced by iterating over the wrapped value.

Raises:

TypeError – If the wrapped object is not iterable.

__contains__(item: Any) bool[source]

Allows using the in operator to check for containment in the wrapped value.

Enables syntax like element in my_observable if the wrapped object (self.get()) supports containment checks (like lists, dictionaries checking keys, sets, strings, or custom objects implementing __contains__).

Parameters:

item (Any) – The item to check for containment within the wrapped value.

Returns:

True if the item is found in the wrapped value, False otherwise.

Return type:

bool

Raises:

TypeError – If the wrapped object does not support the in operator (typically requires __contains__ or being iterable).

sidekick.row module

Provides the Row class for horizontally arranging components in Sidekick.

Use the sidekick.Row class to create a container that lays out its child components horizontally, one after the other, from left to right.

Components are added to a Row in several ways:

  1. By passing the Row instance as the parent argument when creating the child component: my_row = sidekick.Row(instance_id=”button-bar”) button_in_row = sidekick.Button(“Hi”, parent=my_row)

  2. By calling the row.add_child(component) method after both the Row and the child component have been created: my_button = sidekick.Button(“Click”) my_row = sidekick.Row() my_row.add_child(my_button)

  3. By passing existing components directly to the Row constructor: button1 = sidekick.Button(“One”) label1 = sidekick.Label(“Info”) my_row = sidekick.Row(button1, label1)

Rows themselves can be nested within other containers (like Column or another Row). You can also provide an instance_id to uniquely identify the Row.

class sidekick.row.Row(*children: Component, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a Row layout container instance in the Sidekick UI.

Child components added to this container will be arranged horizontally, from left to right.

instance_id

The unique identifier for this row instance.

Type:

str

__init__(*children: Component, instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Row layout container and creates the UI element.

This function is called when you create a new Row, for example: my_row = sidekick.Row() or with initial children: button = sidekick.Button(“OK”) my_row = sidekick.Row(button, parent=another_container, instance_id=”action-row”)

It sends a message to the Sidekick UI to display a new horizontal layout container.

Parameters:
  • *children (Component) – Zero or more Sidekick component instances (e.g., Button, Label, another Row) to be immediately added as children to this row. These child components must already exist. When Row is created, it will attempt to move each of these children into itself.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this Row. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Column) where this Row itself should be placed. If None (the default), the Row is added to the main Sidekick panel area.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error message related to this specific Row container (not its children) is sent back from the Sidekick UI. The function should accept one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate, or if any of children are not valid Sidekick components.

  • TypeError – If parent is an invalid type, or if on_error is provided but is not a callable function.

add_child(child_component: Component)[source]

Moves an existing component into this Row container.

This method sends a command to the Sidekick UI instructing it to make the provided child_component a child of this Row. The child component will be visually placed inside the row layout, typically appended to the end of existing children (to the right of the last child).

Note

The child_component must already exist (i.e., its __init__ must have been called). This method changes the parentage of an existing component, effectively moving it.

Parameters:

child_component (Component) – The Sidekick component instance (e.g., a Button, Textbox, Canvas, another Row) to add as a child to this row.

Raises:
  • TypeError – If child_component is not a valid Sidekick component instance (i.e., not derived from Component).

  • SidekickConnectionError – If sending the update command to the UI fails.

Example

>>> row_container = sidekick.Row(instance_id="my-toolbar")
>>> my_button = sidekick.Button("Click Me") # Initially in root
>>> my_label = sidekick.Label("Info")      # Initially in root
>>>
>>> # Move the button and label into the row
>>> row_container.add_child(my_button)
>>> row_container.add_child(my_label)
>>> # Now my_button and my_label appear horizontally inside row_container

sidekick.server_connector module

Manages the process of connecting to a Sidekick server.

This module defines the ServerConnector class, which is responsible for attempting to establish a communication channel with a Sidekick server. It implements a prioritized connection strategy:

  1. If in a Pyodide environment, it attempts to use the Pyodide-specific bridge.

  2. If a URL has been explicitly set by the user (via sidekick.set_url()), it attempts to connect directly to that URL.

  3. Otherwise, it iterates through a predefined list of default servers (local VS Code extension first, then remote cloud servers) and tries to connect to each one in order.

The connector handles session ID generation and URL modification for servers that require it. Upon a successful connection, it returns details including the active CommunicationManager, any UI URL that should be displayed to the user, and hints about installing the VS Code extension for a better experience.

class sidekick.server_connector.ConnectionAttemptResult(success: bool, communication_manager: CommunicationManager | None = None, final_ws_url: str | None = None, ui_url_to_show: str | None = None, show_ui_url_hint: bool = False, error: Exception | None = None, server_name: str | None = None)[source]

Bases: object

Holds the result of a single attempt to connect to a server.

success

True if the connection attempt was successful.

Type:

bool

communication_manager

The active manager if success.

Type:

Optional[CommunicationManager]

final_ws_url

The actual WebSocket URL used for the attempt.

Type:

Optional[str]

ui_url_to_show

The UI URL to show the user, if applicable.

Type:

Optional[str]

show_ui_url_hint

Flag indicating if a hint to install the VS Code extension should be shown.

Type:

bool

error

The exception encountered if the attempt failed.

Type:

Optional[Exception]

server_name

The name of the server that was attempted.

Type:

Optional[str]

success: bool
communication_manager: CommunicationManager | None = None
final_ws_url: str | None = None
ui_url_to_show: str | None = None
show_ui_url_hint: bool = False
error: Exception | None = None
server_name: str | None = None
class sidekick.server_connector.ConnectionResult(communication_manager: CommunicationManager, ui_url_to_show: str | None = None, show_ui_url_hint: bool = False, server_name: str | None = None)[source]

Bases: object

Holds the details of a successfully established connection.

This object is returned by ServerConnector.connect_async() upon success.

communication_manager

The active and connected manager.

Type:

CommunicationManager

ui_url_to_show

The UI URL to display to the user.

Type:

Optional[str]

show_ui_url_hint

True if a hint about the VS Code extension should be shown.

Type:

bool

server_name

The name of the server for the successful connection.

Type:

Optional[str]

communication_manager: CommunicationManager
ui_url_to_show: str | None = None
show_ui_url_hint: bool = False
server_name: str | None = None
class sidekick.server_connector.ServerConnector(task_manager: TaskManager)[source]

Bases: object

Manages the process of connecting to a Sidekick server.

This class tries different connection strategies in a specific order: 1. Pyodide environment (if applicable). 2. User-defined URL (if set via sidekick.set_url()). 3. A list of default servers (e.g., local VS Code, remote cloud).

It handles session ID generation for remote servers and prepares the necessary information for the ConnectionService to proceed after a successful connection.

__init__(task_manager: TaskManager)[source]

Initializes the ServerConnector.

Parameters:

task_manager (TaskManager) – The TaskManager instance to be used for creating CommunicationManagers.

async connect_async(message_handler: Callable[[str], None | Awaitable[None]] | None, status_change_handler: Callable[[CoreConnectionStatus], None | Awaitable[None]] | None, error_handler: Callable[[Exception], None | Awaitable[None]] | None) ConnectionResult[source]

Attempts to establish a Sidekick connection using various strategies.

It iterates through connection options, and upon finding a successful one, it attaches the provided handlers to the chosen CommunicationManager and starts its listener task.

Parameters:
  • message_handler (Optional[MessageHandlerType]) – The final callback for incoming messages.

  • status_change_handler (Optional[StatusChangeHandlerType]) – The final callback for status changes.

  • error_handler (Optional[ErrorHandlerType]) – The final callback for communication errors.

Returns:

Details of the successfully established connection.

Return type:

ConnectionResult

Raises:

SidekickConnectionError – If all connection attempts fail.

sidekick.textbox module

Provides the Textbox class for creating text input fields in Sidekick.

Use the sidekick.Textbox class to add a single-line text input field to your Sidekick UI panel. Users can type text into this field. When they press Enter or the field loses focus (on-blur), the entered text is sent back to your Python script, triggering a callback function you define, which receives a TextboxSubmitEvent object.

Textboxes can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding them as children to a container’s constructor. You can also provide an instance_id to uniquely identify the textbox.

You can define the textbox’s submission behavior in several ways:

  1. Using the on_submit parameter in the constructor: query_box = sidekick.Textbox(on_submit=handle_query, instance_id=”query-input”)

  2. Using the textbox.on_submit(callback) method after creation: name_input = sidekick.Textbox() name_input.on_submit(process_name)

  3. Using the @textbox.submit decorator: code_input = sidekick.Textbox() @code_input.submit def execute_code(event: sidekick.TextboxSubmitEvent): print(f”Executing: {event.value}”)

class sidekick.textbox.Textbox(value: str = '', placeholder: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_submit: Callable[[TextboxSubmitEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents a single-line Textbox component instance in the Sidekick UI.

Creates an input field where users can type text. Use the on_submit method, the @textbox.submit decorator, or the on_submit constructor parameter to define a Python function that receives a TextboxSubmitEvent (containing the submitted text) when the user submits it (e.g., by pressing Enter or when the input field loses focus).

The value property allows you to programmatically get or set the text currently displayed in the textbox. The placeholder property controls the hint text shown when the textbox is empty.

instance_id

The unique identifier for this textbox instance.

Type:

str

value

The current text content of the textbox.

Type:

str

placeholder

The placeholder text displayed when the box is empty.

Type:

str

__init__(value: str = '', placeholder: str = '', instance_id: str | None = None, parent: Component | str | None = None, on_submit: Callable[[TextboxSubmitEvent], None | Coroutine[Any, Any, None]] | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Textbox object and creates the UI element.

This function is called when you create a new Textbox, for example: user_input = sidekick.Textbox(placeholder=”Enter your name”, on_submit=greet_user)

It sends a message to the Sidekick UI to display a new text input field.

Parameters:
  • value (str) – The text initially displayed in the input field. Defaults to “”.

  • placeholder (str) – Hint text shown when the input field is empty. Defaults to “”.

  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this textbox. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this textbox should be placed. If None (the default), the textbox is added to the main Sidekick panel area.

  • on_submit (Optional[Callable[[TextboxSubmitEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call when the user submits text from this textbox. The function should accept one TextboxSubmitEvent object as an argument, which contains instance_id, type, and value (the submitted text). The callback can be a regular function or a coroutine function (async def). Defaults to None.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error related to this specific textbox occurs in the Sidekick UI. The function should take one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_submit or on_error are provided but are not callable functions.

property value: str

The current text content of the textbox.

Reading this property returns the value stored locally in the Python object. This local value is updated whenever the user submits text from the UI (triggering an on_submit event that carries a TextboxSubmitEvent with the new value).

Setting this property (e.g., my_textbox.value = “New text”) updates the local value and also sends a command to update the text displayed in the Sidekick UI’s input field.

Type:

str

property placeholder: str

The placeholder text displayed when the textbox is empty.

Setting this property (e.g., my_textbox.placeholder = “Enter command…”) updates the placeholder text in the Sidekick UI.

Type:

str

on_submit(callback: Callable[[TextboxSubmitEvent], None | Coroutine[Any, Any, None]] | None)[source]

Registers a function to call when the user submits text from this textbox.

The submission typically happens when the user presses Enter while the textbox has focus, or when the input field loses focus (on-blur event). The provided callback function will receive a TextboxSubmitEvent object, which contains the instance_id of this textbox, the event type (“submit”), and the value (the submitted text string).

You can also set this callback directly when creating the textbox using the on_submit parameter in its constructor.

Parameters:

callback (Optional[Callable[[TextboxSubmitEvent], Union[None, Coroutine[Any, Any, None]]]]) – The function to call on submit. It must accept one TextboxSubmitEvent argument. The callback can be a regular function or a coroutine function (async def). Pass None to remove the current callback.

Raises:

TypeError – If callback is not a callable function or None.

Example

>>> from sidekick.events import TextboxSubmitEvent
>>>
>>> def process_input(event: TextboxSubmitEvent):
...     print(f"Textbox '{event.instance_id}' submitted: {event.value}")
...
>>> entry_field = sidekick.Textbox(instance_id="user-entry")
>>> entry_field.on_submit(process_input)
>>> # sidekick.run_forever() # Needed to process submissions
submit(func: Callable[[TextboxSubmitEvent], None | Coroutine[Any, Any, None]]) Callable[[TextboxSubmitEvent], None | Coroutine[Any, Any, None]][source]

Decorator to register a function to call when the user submits text.

This provides an alternative, more Pythonic syntax to on_submit() if you prefer decorators. The decorated function will receive a TextboxSubmitEvent object as its argument.

Parameters:

func (Callable[[TextboxSubmitEvent], Union[None, Coroutine[Any, Any, None]]]) – The function to register as the submit handler. It must accept one TextboxSubmitEvent argument. The callback can be a regular function or a coroutine function (async def).

Returns:

The original function, allowing the decorator to be used directly.

Return type:

Callable[[TextboxSubmitEvent], Union[None, Coroutine[Any, Any, None]]]

Raises:

TypeError – If func is not a callable function.

Example

>>> from sidekick.events import TextboxSubmitEvent
>>>
>>> name_input = sidekick.Textbox(placeholder="Enter name", instance_id="name-field")
>>>
>>> @name_input.submit
... def handle_name(event: TextboxSubmitEvent):
...     print(f"Hello, {event.value} (from '{event.instance_id}')!")
...
>>> # sidekick.run_forever() # Needed to process submissions

sidekick.utils module

Internal Utility Functions for the Sidekick Library.

This module contains helper functions used internally by other parts of the Sidekick library.

Warning

Functions and variables in this module are considered internal implementation details. You should not need to import or use them directly in your scripts, as they might change without notice in future versions.

sidekick.utils.generate_unique_id(prefix: str) str[source]

Generates a simple, sequential unique ID for a component instance.

This function is used internally by Sidekick component classes (like Grid, Console, etc.) when you create an instance without providing your own specific instance_id. It ensures that each automatically generated ID is unique within the current script run, helping the library distinguish between different components of the same type (e.g., multiple Grids).

The generated IDs follow a simple “prefix-number” format (e.g., “grid-1”, “console-2”).

Note

This is intended for internal library use. You should not rely on the specific format of these generated IDs in your code, as it could change. Always use the instance_id attribute of a component instance if you need to reference its ID.

Parameters:

prefix (str) – A descriptive prefix indicating the type of component, such as “grid”, “console”, or “canvas”.

Returns:

A unique identifier string for the new instance (e.g., “grid-1”).

Return type:

str

sidekick.utils.generate_session_id() str[source]

Generates a session ID.

It tries to load an existing session ID from a file in the user’s application data directory. If a standard directory cannot be determined, or if the ID is not found, it generates a new ID.

If a standard app data directory isn’t found, the generated ID is transient and not saved to disk. Otherwise, it saves the new ID to disk and returns it.

sidekick.viz module

Provides the Viz class for visualizing Python variables in Sidekick.

Use the sidekick.Viz class to create an interactive, tree-like display of your Python variables within the Sidekick panel in VS Code. This is incredibly helpful for understanding the state and structure of your data, especially complex objects, lists, dictionaries, and sets, as your script executes.

The Viz panel can be placed inside layout containers like Row or Column by specifying the parent during initialization, or by adding it as a child to a container’s constructor. You can also provide an instance_id to uniquely identify the Viz panel.

Key Features:

  • Variable Inspection: Display almost any Python variable (int, str, list, dict, set, custom objects) using the show() method. The Viz panel presents nested structures in a collapsible tree view.

  • Reactivity (with ObservableValue): The most powerful feature! If you wrap your mutable data (lists, dicts, sets) in sidekick.ObservableValue before showing it (viz.show(“my_data”, sidekick.ObservableValue(data))), the Viz panel will automatically update its display whenever you modify the data through the ObservableValue wrapper. Changes are often highlighted, making it easy to see exactly what happened.

  • Clear Display: Handle large collections and deep nesting by truncating the display automatically. Detect and visualize recursive references to prevent infinite loops.

  • Variable Removal: Remove variables from the display when they are no longer needed using remove_variable().

Basic Usage:
>>> import sidekick
>>> viz = sidekick.Viz(instance_id="main-data-viewer")
>>> my_config = {"user": "Alice", "settings": {"theme": "dark", "level": 5}}
>>> viz.show("App Config", my_config)
Reactive Usage with a Parent Container:
>>> import sidekick
>>> my_column = sidekick.Column()
>>> viz_in_col = sidekick.Viz(parent=my_column, instance_id="reactive-viz")
>>> reactive_list = sidekick.ObservableValue([10, 20])
>>> viz_in_col.show("Reactive List", reactive_list)
>>> reactive_list.append(30) # Viz updates automatically
>>> # sidekick.run_forever() # Needed for ObservableValue updates in some scenarios
class sidekick.viz.Viz(instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Bases: Component

Represents the Variable Visualizer (Viz) component instance in the Sidekick UI.

Creates an interactive panel for displaying Python variables and data structures. This is particularly useful for inspecting lists, dictionaries, sets, and custom objects as your script runs.

When used with sidekick.ObservableValue, the Viz panel can automatically update its display when the wrapped data changes, providing a reactive view of your program’s state.

The Viz panel can be nested within layout containers like Row or Column.

instance_id

The unique identifier for this Viz panel instance.

Type:

str

__init__(instance_id: str | None = None, parent: Component | str | None = None, on_error: Callable[[ErrorEvent], None | Coroutine[Any, Any, None]] | None = None)[source]

Initializes the Viz object and creates the UI panel.

This function is called when you create a new Viz panel, for example: data_viewer = sidekick.Viz()

It sends a message to the Sidekick UI to display a new variable visualization panel.

Parameters:
  • instance_id (Optional[str]) – An optional, user-defined unique identifier for this Viz panel. If None, an ID will be auto-generated. Must be unique if provided.

  • parent (Optional[Union['Component', str]]) – The parent container (e.g., a sidekick.Row or sidekick.Column) where this Viz panel should be placed. If None (the default), the Viz panel is added to the main Sidekick panel area.

  • on_error (Optional[Callable[[ErrorEvent], Union[None, Coroutine[Any, Any, None]]]]) – A function to call if an error message related to this specific Viz panel (not necessarily the variables it’s displaying) occurs in the Sidekick UI. The function should accept one ErrorEvent object as an argument. The callback can be a regular function or a coroutine function (async def). Defaults to None.

Raises:
  • ValueError – If the provided instance_id is invalid or a duplicate.

  • TypeError – If parent is an invalid type, or if on_error is provided but is not a callable function.

show(name: str, value: Any)[source]

Displays or updates a Python variable in this Sidekick Viz panel.

Call this method to make a Python variable visible in the Viz panel. If a variable with the same name was already shown, its display will be updated to reflect the new value.

If the value you provide is an instance of sidekick.ObservableValue, the Viz panel will automatically subscribe to changes in that observable. This means that when you modify the data through the ObservableValue wrapper (e.g., my_observable_list.append(item)), the Viz display will update in real-time without needing to call viz.show() again.

For non-observable values, you must call viz.show() again with the same name if the value changes and you want the Viz panel to reflect that change.

Parameters:
  • name (str) – The name to display for this variable in the Viz panel. This name acts as the identifier for the variable within this Viz instance. It must be a non-empty string.

  • value (Any) – The Python variable or value you want to visualize. This can be any Python object (numbers, strings, lists, dicts, sets, custom objects, or ObservableValue instances).

Raises:
remove_variable(name: str)[source]

Removes a previously shown variable from this Viz panel display.

If the variable was an ObservableValue, this method will also automatically unsubscribe the Viz panel from its updates. Calling this for a variable name that is not currently shown has no effect and logs a warning.

Parameters:

name (str) – The exact name of the variable to remove from the Viz display, as previously used in viz.show(name, …).

Raises:

SidekickConnectionError – If sending the command to the UI fails.

remove()[source]

Removes the entire Viz panel instance from the Sidekick UI.

This also ensures that the Viz panel unsubscribes from all ObservableValue instances it was tracking, preventing potential memory leaks or unwanted callbacks after the panel is gone.

Core Submodules

sidekick.core.communication_manager module

Defines the abstract base class for managing low-level communication channels.

This module provides the CommunicationManager Abstract Base Class (ABC). Concrete implementations of this class will handle the specifics of different communication transports, such as WebSockets (for CPython environments) or JavaScript postMessage (for Pyodide environments).

The CommunicationManager is responsible for:

  • Establishing and tearing down the raw communication link (e.g., WebSocket connection).

  • Sending raw string-based messages over the established link.

  • Receiving raw string-based messages from the link.

  • Reporting changes in the connection status (using CoreConnectionStatus).

  • Notifying about underlying communication errors that might occur.

  • Allowing higher-level services (like ConnectionService) to provide callback functions during connection to handle incoming messages, status changes, and errors.

This abstraction allows the rest of the Sidekick system to interact with different communication methods through a consistent interface.

sidekick.core.communication_manager.MessageHandlerType

Type alias for a function that handles incoming raw string messages. It accepts the message string and returns None or an Awaitable.

alias of Callable[[str], None | Awaitable[None]]

sidekick.core.communication_manager.StatusChangeHandlerType

Type alias for a function that handles connection status changes. It accepts a CoreConnectionStatus enum member and returns None or an Awaitable.

alias of Callable[[CoreConnectionStatus], None | Awaitable[None]]

sidekick.core.communication_manager.ErrorHandlerType

Type alias for a function that handles low-level communication errors. It accepts an Exception object and returns None or an Awaitable.

alias of Callable[[Exception], None | Awaitable[None]]

class sidekick.core.communication_manager.CommunicationManager[source]

Bases: ABC

Abstract Base Class for managing a raw communication channel.

Implementations of this class will abstract the specifics of the underlying transport mechanism (e.g., WebSockets, Pyodide’s JavaScript message passing) and provide a standardized interface for: - Connecting to and disconnecting from a remote endpoint, accepting handlers at connect time. - Sending and receiving string-based messages. - Monitoring the connection status.

abstractmethod async connect_async(message_handler: Callable[[str], None | Awaitable[None]] | None = None, status_change_handler: Callable[[CoreConnectionStatus], None | Awaitable[None]] | None = None, error_handler: Callable[[Exception], None | Awaitable[None]] | None = None) None[source]

Establishes the connection to the remote endpoint asynchronously.

Implementations should handle the complete process of setting up the communication link according to the chosen transport protocol (e.g., performing a WebSocket handshake). The provided handlers should be stored and used by the CommunicationManager for the duration of this connection.

Upon successful connection, the manager’s status should transition to CoreConnectionStatus.CONNECTED. If the connection attempt fails, an appropriate CoreConnectionError (such as CoreConnectionRefusedError or CoreConnectionTimeoutError) should be raised to indicate the failure.

Parameters:
  • message_handler (Optional[MessageHandlerType]) – Callback for incoming messages.

  • status_change_handler (Optional[StatusChangeHandlerType]) – Callback for status changes.

  • error_handler (Optional[ErrorHandlerType]) – Callback for communication errors.

Raises:

CoreConnectionError – If the connection cannot be established due to refusal, timeout, or other transport-level issues.

abstractmethod async close_async() None[source]

Closes the communication channel asynchronously.

Implementations should gracefully terminate the connection. This might involve sending close frames (for WebSockets) or releasing resources. The status should typically transition through CoreConnectionStatus.CLOSING and eventually to CoreConnectionStatus.DISCONNECTED. This method should be idempotent; calling it multiple times on an already closed or closing connection should not cause errors.

abstractmethod async send_message_async(message_str: str) None[source]

Sends a string message over the communication channel asynchronously.

The caller is responsible for ensuring the message_str is formatted according to the expected protocol (e.g., as a JSON string).

Parameters:

message_str (str) – The raw string message to send.

Raises:
  • CoreDisconnectedError – If the channel is not currently connected or if the send operation fails due to a broken or closed connection.

  • Exception – Other transport-specific exceptions might be raised if the send fails for reasons other than disconnection (e.g., message too large for buffer, underlying socket errors).

abstractmethod is_connected() bool[source]

Checks if the communication channel is currently in an active, connected state.

A “connected” state typically means the status is CoreConnectionStatus.CONNECTED and the underlying transport link is open and usable for sending/receiving data.

Returns:

True if the channel is actively connected, False otherwise.

Return type:

bool

abstractmethod get_current_status() CoreConnectionStatus[source]

Returns the current CoreConnectionStatus of the communication channel.

This provides the most up-to-date known state of the connection according to the manager.

Returns:

The current connection status.

Return type:

CoreConnectionStatus

sidekick.core.cpython_task_manager module

CPython-specific implementation of the TaskManager.

This module provides CPythonTaskManager, a concrete implementation of the TaskManager abstract base class. It is designed for standard CPython environments where asyncio operations need to be managed alongside potentially synchronous application code.

The CPythonTaskManager achieves this by running an asyncio event loop in a separate daemon thread. This allows the main application thread to remain synchronous (e.g., blocking on user input or waiting for the service to stop) while asynchronous tasks (like network communication or timed events) are handled concurrently by the event loop in its dedicated thread.

class sidekick.core.cpython_task_manager.CPythonTaskManager[source]

Bases: TaskManager

Manages an asyncio event loop in a separate thread for CPython environments.

This implementation starts an asyncio event loop in a dedicated daemon thread, allowing the main synchronous Python program to schedule and interact with asynchronous tasks. It handles thread-safe submission of coroutines and graceful shutdown of the loop and its tasks.

__init__()[source]

Initializes the CPythonTaskManager.

The event loop and its thread are not started automatically. They are started on-demand by the first call to a method requiring the loop, typically ensure_loop_running().

ensure_loop_running() None[source]

Ensures the asyncio event loop is created and running in its dedicated thread.

is_loop_running() bool[source]

Checks if the managed asyncio event loop is active and responsive.

get_loop() AbstractEventLoop[source]

Returns the managed asyncio event loop, ensuring it’s running first.

submit_task(coro: Coroutine[Any, Any, Any]) Task[source]

Submits a coroutine to the managed event loop thread-safely.

create_event() Event[source]

Creates an asyncio.Event object associated with the managed event loop.

stop_loop() None[source]

Requests the managed event loop to begin its shutdown process.

wait_for_stop() None[source]

Blocks the calling thread until the managed event loop has fully stopped.

sidekick.core.exceptions module

Core exceptions for the Sidekick library’s underlying infrastructure.

This module defines a hierarchy of custom exceptions that are used by the core components of the Sidekick library, such as the TaskManager and CommunicationManager.

These exceptions are intended to be relatively generic and focused on the fundamental operations of these core components. Higher-level, application-specific Sidekick exceptions (e.g., those related to UI interactions or specific Sidekick features) may inherit from or wrap these core exceptions.

exception sidekick.core.exceptions.CoreBaseError(message: str, original_exception: BaseException | None = None)[source]

Bases: Exception

Base class for all core errors within the Sidekick library infrastructure.

__str__() str[source]

Provide a more informative string representation.

exception sidekick.core.exceptions.CoreConnectionError(message: str, url: str | None = None, original_exception: BaseException | None = None)[source]

Bases: CoreBaseError

Base class for errors related to the core communication channel.

This exception is raised for issues encountered by a CommunicationManager instance, such as problems establishing a connection or maintaining an active link.

__str__() str[source]

Provide a more informative string representation.

exception sidekick.core.exceptions.CoreConnectionEstablishmentError(message: str, url: str | None = None, original_exception: BaseException | None = None)[source]

Bases: CoreConnectionError

Base class for errors that occur specifically during the connection establishment phase.

This type of error indicates that the CommunicationManager was unable to successfully initiate and complete the connection process to the remote endpoint.

exception sidekick.core.exceptions.CoreConnectionRefusedError(url: str, original_exception: BaseException | None = None)[source]

Bases: CoreConnectionEstablishmentError

Raised when a connection attempt is actively refused by the remote endpoint.

This typically means that no service is listening at the specified host and port, or a firewall is blocking the connection.

url

The target URL that the connection attempt was made to.

Type:

str

original_exception

The lower-level exception (e.g., ConnectionRefusedError from the OS) that caused this failure, if any.

Type:

Optional[BaseException]

exception sidekick.core.exceptions.CoreConnectionTimeoutError(url: str, timeout_seconds: float | None = None, original_exception: BaseException | None = None)[source]

Bases: CoreConnectionEstablishmentError

Raised when a connection attempt times out before it can be established.

This indicates that the remote endpoint did not respond within the expected timeframe during the connection handshake.

url

The target URL that the connection attempt was made to.

Type:

str

timeout_seconds

The duration of the timeout in seconds, if available.

Type:

Optional[float]

original_exception

The lower-level timeout exception, if any.

Type:

Optional[BaseException]

exception sidekick.core.exceptions.CoreDisconnectedError(message: str = 'The communication channel is disconnected.', reason: str | None = None, url: str | None = None, original_exception: BaseException | None = None)[source]

Bases: CoreConnectionError

Raised when an operation is attempted on a disconnected or closed channel, or when an established connection is unexpectedly lost.

This error signifies that the communication channel is not currently active and cannot perform the requested operation.

reason

An optional string describing the reason for disconnection.

Type:

Optional[str]

exception sidekick.core.exceptions.CoreTaskManagerError(message: str, original_exception: BaseException | None = None)[source]

Bases: CoreBaseError

Base class for errors related to the core TaskManager.

This exception is raised for issues encountered during the operation of the TaskManager, such as problems submitting tasks or managing the event loop.

exception sidekick.core.exceptions.CoreLoopNotRunningError(message: str = "The TaskManager's event loop is not running.")[source]

Bases: CoreTaskManagerError, RuntimeError

Raised when an operation requires the TaskManager’s event loop to be running, but it is not.

This might occur if ensure_loop_running() has not been called or if the loop has been stopped.

exception sidekick.core.exceptions.CoreTaskSubmissionError(message: str = 'Failed to submit task to the TaskManager.', original_exception: BaseException | None = None)[source]

Bases: CoreTaskManagerError

Raised when there is an error submitting a task to the TaskManager.

This could be due to various reasons, such as the loop not being ready or an issue with the task itself during submission.

original_exception

The lower-level exception that occurred during task submission, if any.

Type:

Optional[BaseException]

__str__() str[source]

Provide a more informative string representation.

sidekick.core.factories module

Factory functions for creating core infrastructure components.

This module provides centralized functions for instantiating core components like the TaskManager and specific types of CommunicationManagers. The TaskManager is provided as a singleton, while CommunicationManagers are created on demand.

sidekick.core.factories.get_task_manager() TaskManager[source]

Gets the singleton instance of the appropriate TaskManager.

This function determines whether the code is running in a CPython or Pyodide environment and returns a corresponding TaskManager implementation. The TaskManager instance is created only once (singleton pattern).

Subsequent calls to this function will return the same instance.

Returns:

The singleton TaskManager instance for the current environment.

Return type:

TaskManager

Raises:

RuntimeError – If a TaskManager instance cannot be created for some unexpected reason.

sidekick.core.factories.create_websocket_communication_manager(url: str, task_manager: TaskManager) WebSocketCommunicationManager[source]

Creates and returns a new instance of WebSocketCommunicationManager.

This manager is responsible for handling WebSocket communication, typically used in CPython environments.

Parameters:
  • url (str) – The WebSocket URL to connect to (e.g., “ws://localhost:5163”).

  • task_manager (TaskManager) – The TaskManager instance that this CommunicationManager will use for scheduling its asynchronous operations.

Returns:

A new instance configured for the given URL.

Return type:

WebSocketCommunicationManager

sidekick.core.factories.create_pyodide_communication_manager(task_manager: TaskManager) PyodideCommunicationManager[source]

Creates and returns a new instance of PyodideCommunicationManager.

This manager handles communication within a Pyodide environment, typically by bridging messages between a Web Worker (where Python runs) and the main browser thread (where the UI runs).

Parameters:

task_manager (TaskManager) – The TaskManager instance (usually PyodideTaskManager) that this CommunicationManager will use.

Returns:

A new instance.

Return type:

PyodideCommunicationManager

sidekick.core.pyodide_communication_manager module

Pyodide-specific implementation of the CommunicationManager.

This module provides PyodideCommunicationManager, which facilitates communication when the Sidekick Python library is running within a Pyodide environment (typically inside a Web Worker). Instead of using WebSockets, it relies on direct JavaScript function calls, brokered by the Pyodide runtime, to exchange messages with the Sidekick UI running on the main browser thread.

The manager assumes that specific JavaScript functions are globally available in the JavaScript context where Pyodide is operating. These functions are responsible for relaying messages to/from the main thread UI.

class sidekick.core.pyodide_communication_manager.PyodideCommunicationManager(task_manager: TaskManager)[source]

Bases: CommunicationManager

Manages communication using Pyodide’s JavaScript bridge.

This class implements the CommunicationManager interface for Pyodide environments. It assumes it’s running within a Pyodide Web Worker and that the main browser thread (hosting the UI) has exposed JavaScript functions: - js.registerSidekickMessageHandler(proxy_to_python_callback):

Called by Python to give JavaScript a way to send messages to Python.

  • js.sendHeroMessage(message_string): Called by Python to send a message string to the JavaScript side (UI).

The “connection” is considered established once the Python message handler is successfully registered with the JavaScript side.

__init__(task_manager: TaskManager)[source]

Initializes the PyodideCommunicationManager.

Parameters:

task_manager (TaskManager) – The TaskManager instance (typically PyodideTaskManager) for scheduling asynchronous operations like handler invocations.

async connect_async(message_handler: Callable[[str], None | Awaitable[None]] | None = None, status_change_handler: Callable[[CoreConnectionStatus], None | Awaitable[None]] | None = None, error_handler: Callable[[Exception], None | Awaitable[None]] | None = None) None[source]

Establishes the communication bridge to JavaScript.

For Pyodide, “connecting” means registering a Python callback function (via a Pyodide proxy) with the JavaScript environment, allowing JavaScript to send messages to this Python instance.

async send_message_async(message_str: str) None[source]

Sends a string message to the Sidekick UI via the JavaScript bridge.

Parameters:

message_str (str) – The message string to send (expected to be JSON).

Raises:

CoreDisconnectedError – If the manager is not connected.

async close_async() None[source]

Closes the communication bridge by destroying the JavaScript proxy.

This effectively unregisters the Python message handler from the JavaScript side, stopping further messages from being received.

is_connected() bool[source]

Checks if the communication bridge to JavaScript is considered active.

get_current_status() CoreConnectionStatus[source]

Returns the current CoreConnectionStatus of the Pyodide bridge.

sidekick.core.pyodide_task_manager module

Pyodide-specific implementation of the TaskManager.

This TaskManager leverages the existing asyncio event loop provided by the Pyodide environment (typically running in a Web Worker). It does not create a new thread or manage the loop’s lifecycle directly, as that is handled by the browser and Pyodide runtime.

This class acts as a lightweight adapter to the externally-managed event loop, conforming to the TaskManager interface.

class sidekick.core.pyodide_task_manager.PyodideTaskManager[source]

Bases: TaskManager

Manages tasks using Pyodide’s existing asyncio event loop.

This implementation assumes that an asyncio event loop is already running or will be run by the Pyodide environment (e.g., when Python code is executed via pyodide.runPythonAsync). It acts as an interface to this externally managed loop.

__init__()[source]

Initializes the PyodideTaskManager.

ensure_loop_running() None[source]

Ensures the loop reference is initialized. The loop itself is managed by Pyodide.

is_loop_running() bool[source]

Checks if the Pyodide-managed asyncio event loop is currently running.

get_loop() AbstractEventLoop[source]

Returns the Pyodide-managed asyncio event loop.

submit_task(coro: Coroutine[Any, Any, Any]) Task[source]

Submits a coroutine to Pyodide’s event loop.

Parameters:

coro (Coroutine[Any, Any, Any]) – The coroutine to execute.

Returns:

The task object representing the coroutine’s execution.

Return type:

asyncio.Task

Raises:

CoreTaskSubmissionError – If creating the task fails.

create_event() Event[source]

Creates an asyncio.Event object associated with the managed event loop.

stop_loop() None[source]

Does nothing in the Pyodide environment.

The Pyodide/browser event loop lifecycle is not managed by this TaskManager and should not be stopped from Python code, as it would terminate the entire Web Worker’s execution context. This method is a no-op to fulfill the TaskManager interface contract.

wait_for_stop() None[source]

Immediately returns in the Pyodide environment.

Since the event loop cannot be stopped by this manager, there is no ‘stopped’ state to wait for. This method is a no-op to maintain interface compatibility.

For synchronous applications that need to wait, this pattern is not suitable for Pyodide. Asynchronous waiting should be used instead.

sidekick.core.status module

Defines the core connection status for communication channels.

This module provides the CoreConnectionStatus enumeration, which represents the various states an underlying communication channel (like a WebSocket or a Pyodide message bridge) can be in. This is a low-level status, distinct from higher-level application-specific connection states.

class sidekick.core.status.CoreConnectionStatus(*values)[source]

Bases: Enum

Represents the fundamental states of a communication channel.

This enum is used by the CommunicationManager to track the health and status of the connection it manages.

DISCONNECTED = 1

The channel is not connected. This is the initial state, or the state after a connection has been explicitly closed or has definitively failed and will not attempt automatic reconnection at this level.

CONNECTING = 2

The channel is actively attempting to establish a connection. For example, a WebSocket client might be in the process of its handshake.

CONNECTED = 3

The channel has successfully established an active connection. Data can (in principle) be sent and received over the transport layer. For example, a WebSocket connection is open.

CLOSING = 4

The channel is in the process of gracefully closing the connection. This state might be entered before transitioning to DISCONNECTED during a clean shutdown.

ERROR = 5

The channel has encountered an unrecoverable error. The connection is not usable and likely needs to be re-initialized (e.g., by creating a new channel instance) if communication is to be re-attempted.

__str__() str[source]

Return a user-friendly string representation of the status.

sidekick.core.task_manager module

Defines the abstract base class for managing asyncio event loops and tasks.

This module provides the TaskManager Abstract Base Class (ABC), which outlines the contract for concrete implementations that manage an asyncio event loop. Different implementations exist for standard CPython environments (managing a loop in a separate thread) and for Pyodide environments (using the browser’s event loop provided by Pyodide).

The TaskManager is responsible for:

  • Ensuring an event loop is running and providing access to it.

  • Submitting asynchronous tasks (coroutines) to be executed on the loop.

  • Providing mechanisms to request the loop to stop and to wait for its completion.

  • Creating event loop-specific synchronization primitives like asyncio.Event.

class sidekick.core.task_manager.TaskManager[source]

Bases: ABC

Abstract Base Class for managing an asyncio event loop and tasks.

This class defines the interface for an environment-aware asyncio loop manager. Concrete subclasses will provide environment-specific implementations for CPython and Pyodide. Its primary role is to provide a stable asynchronous execution context for other services, like the ConnectionService.

abstractmethod ensure_loop_running() None[source]

Ensures that the managed asyncio event loop is running.

If the loop is not already running (e.g., in CPython where it might run in a separate thread), this method should start it and confirm its readiness. If the loop is already running, this method should do nothing.

Raises:

CoreTaskManagerError – If the loop cannot be started or confirmed as running.

abstractmethod get_loop() AbstractEventLoop[source]

Returns the asyncio event loop instance managed by this TaskManager.

This method should typically call ensure_loop_running() implicitly to guarantee that a running loop is returned.

Returns:

The event loop instance.

Return type:

asyncio.AbstractEventLoop

Raises:

CoreLoopNotRunningError – If the loop has not been initialized or is not accessible.

abstractmethod is_loop_running() bool[source]

Checks if the managed asyncio event loop is currently running.

Returns:

True if the loop is active and responsive, False otherwise.

Return type:

bool

abstractmethod submit_task(coro: Coroutine[Any, Any, Any]) Task[source]

Submits a coroutine to be executed on the managed event loop.

This method is non-blocking. It schedules the coroutine and should immediately return an asyncio.Task object. For CPython, this method must be thread-safe.

Parameters:

coro (Coroutine[Any, Any, Any]) – The coroutine to execute.

Returns:

The task object representing the execution of the coroutine.

Callers can use this to await the task’s completion or to cancel it.

Return type:

asyncio.Task

abstractmethod create_event() Event[source]

Creates an asyncio.Event object associated with the managed event loop.

This is a factory method to ensure that synchronization primitives are created in the correct loop context, which is especially important in multi-threaded CPython environments. For CPython, this method must be thread-safe.

Returns:

A new event object tied to the managed loop.

Return type:

asyncio.Event

abstractmethod stop_loop() None[source]

Requests the managed event loop to begin its shutdown process.

This method is non-blocking and thread-safe. It signals the loop to stop processing new tasks and to eventually terminate. For implementations that manage the loop’s lifecycle (like CPythonTaskManager), this will lead to the loop stopping. For implementations that don’t own the loop (like PyodideTaskManager), this may be a no-op.

abstractmethod wait_for_stop() None[source]

Blocks the calling thread until the managed event loop has fully stopped.

This method is synchronous and blocking. It’s primarily intended for the main thread of a CPython application to wait for the background event loop thread to terminate cleanly.

Note

This method may not be applicable or may be a no-op in environments where the loop’s lifecycle is not managed by the TaskManager (e.g., Pyodide).

sidekick.core.utils module

Core utility functions for the Sidekick library’s underlying infrastructure.

This module provides helper functions that are used by other modules within the sidekick.core sub-package. These utilities are generally low-level and support the fundamental operations of the core components.

sidekick.core.utils.is_pyodide() bool[source]

Checks if the Python code is currently running in a Pyodide environment.

This function determines the execution environment by checking common indicators associated with Pyodide, such as the presence of the ‘pyodide’ module or the ‘sys.platform’ value being ‘emscripten’.

The result is cached after the first call for efficiency.

Returns:

True if the environment is detected as Pyodide, False otherwise.

Return type:

bool

Example

>>> from sidekick.core.utils import is_pyodide
>>> if is_pyodide():
...     print("Running in Pyodide!")
... else:
...     print("Running in a standard CPython environment (or similar).")

sidekick.core.websocket_communication_manager module

WebSocket-based implementation of the CommunicationManager.

This module provides WebSocketCommunicationManager, which uses the websockets library to manage a WebSocket connection to a remote server. It handles sending and receiving messages, managing connection state, and invoking registered handlers for messages, status changes, and errors. This implementation is typically used in standard CPython environments.

class sidekick.core.websocket_communication_manager.WebSocketCommunicationManager(url: str, task_manager: TaskManager, open_timeout: float | None = 5.0, ping_interval: float | None = 20.0, ping_timeout: float | None = 10.0, close_timeout: float | None = 1.0)[source]

Bases: CommunicationManager

Manages a WebSocket connection using the websockets library.

This class implements the CommunicationManager interface for CPython environments, providing asynchronous WebSocket communication capabilities. It handles the connection lifecycle, message transmission and reception, and keep-alive pings.

__init__(url: str, task_manager: TaskManager, open_timeout: float | None = 5.0, ping_interval: float | None = 20.0, ping_timeout: float | None = 10.0, close_timeout: float | None = 1.0)[source]

Initializes the WebSocketCommunicationManager.

Parameters:
  • url (str) – The WebSocket URL to connect to (e.g., “ws://localhost:5163”).

  • task_manager (TaskManager) – The TaskManager instance that this CommunicationManager will use for scheduling its asynchronous operations, such as the message listener loop.

  • open_timeout (Optional[float]) – Timeout for establishing the initial connection.

  • ping_interval (Optional[float]) – Interval for sending keep-alive pings. If None, client-side pings are disabled.

  • ping_timeout (Optional[float]) – Timeout for waiting for a pong response.

  • close_timeout (Optional[float]) – Timeout for the graceful close handshake.

async connect_async(message_handler: Callable[[str], None | Awaitable[None]] | None = None, status_change_handler: Callable[[CoreConnectionStatus], None | Awaitable[None]] | None = None, error_handler: Callable[[Exception], None | Awaitable[None]] | None = None) None[source]

Establishes or configures the WebSocket connection.

This method can be called in two phases: 1. Initial connection: Called with no handlers to simply establish the

WebSocket link. If successful, the status becomes CONNECTED.

  1. Handler attachment: Called on an already connected instance to attach or update the message/status/error handlers and start the message listener task if it’s not already running.

Parameters:
  • message_handler (Optional[MessageHandlerType]) – Callback for incoming messages.

  • status_change_handler (Optional[StatusChangeHandlerType]) – Callback for status changes.

  • error_handler (Optional[ErrorHandlerType]) – Callback for communication errors.

Raises:

CoreConnectionError – If the connection attempt fails.

async send_message_async(message_str: str) None[source]

Sends a string message over the active WebSocket connection.

async close_async() None[source]

Closes the WebSocket connection asynchronously.

is_connected() bool[source]

Checks if the WebSocket is actively connected.

get_current_status() CoreConnectionStatus[source]

Returns the current CoreConnectionStatus.