# Adding Components to `pydase` This guide provides a step-by-step process for adding new components to the `pydase` package. Components in `pydase` consist of both backend (Python) and frontend (React) parts. They work together to create interactive and dynamic data services. ## Overview A component in `pydase` is a unique combination of a backend class (e.g., `Image`) and its corresponding frontend React component. The backend class stores the attributes needed for the component, and possibly methods for setting those in the backend, while the frontend part is responsible for rendering and interacting with the component. ## Adding a Backend Component to `pydase` Backend components belong in the `src/pydase/components` directory. ### Step 1: Create a New Python File in the Components Directory Navigate to the `src/pydase/components` directory and create a new Python file for your component. The name of the file should be descriptive of the component's functionality. For example, for a `Image` component, create a file named `image.py`. ### Step 2: Define the Backend Class Within the newly created file, define a Python class representing the component. This class should inherit from `DataService` and contains the attributes that the frontend needs to render the component. Every public attribute defined in this class will synchronise across the clients. It can also contain methods which can be used to interact with the component from the backend. For the `Image` component, the class may look like this: ```python # file: pydase/components/image.py from pydase.data_service.data_service import DataService class Image(DataService): def __init__( self, image_representation: bytes = b"", ) -> None: self.image_representation = image_representation super().__init__() # need to decode the bytes def __setattr__(self, __name: str, __value: Any) -> None: if __name == "value": if isinstance(__value, bytes): __value = __value.decode() return super().__setattr__(__name, __value) ``` So, changing the `image_representation` will push the updated value to the browsers connected to the service. ### Step 3: Register the Backend Class The component should be added to the `__init__.py` file to ensure `pydase` handles them properly: ```python # file: pydase/components/__init__.py from pydase.components.image import Image from pydase.components.number_slider import NumberSlider __all__ = [ "NumberSlider", "Image", # add the new components here ] ``` ### Step 4: Implement Necessary Methods (Optional) If your component requires specific logic or methods, implement them within the class. Document any public methods or attributes to ensure that other developers understand their purpose and usage. ### Step 5: Write Tests for the Component (Recommended) Consider writing unit tests for the component to verify its behavior. Place the tests in the appropriate directory within the `tests` folder. For example, a test for the `Image` component could look like this: ```python from pytest import CaptureFixture from pydase.components.image import Image from pydase.data_service.data_service import DataService def test_Image(capsys: CaptureFixture) -> None: class ServiceClass(DataService): image = Image() service = ServiceClass() # ... ``` ## Adding a Frontend Component to `pydase` Frontend components in `pydase` live in the `frontend/src/components/` directory. Follow these steps to create and add a new frontend component: ### Step 1: Create a New React Component File in the Components Directory Navigate to the `frontend/src/components/` directory and create a new React component file for your component. The name of the file should be descriptive of the component's functionality and reflect the naming conventions used in your project. For example, for an `Image` component, create a file named `ImageComponent.tsx`. ### Step 2: Write the React Component Code Write the React component code, following the structure and patterns used in existing components. Make sure to import necessary libraries and dependencies. For example, for the `Image` component, a template could look like this: ```tsx import { emit_update } from '../socket'; // use this when your component should update values in the backend import { DocStringComponent } from './DocStringComponent'; import React, { useEffect, useRef, useState } from 'react'; import { Card, Collapse, Image } from 'react-bootstrap'; import { DocStringComponent } from './DocStringComponent'; import { ChevronDown, ChevronRight } from 'react-bootstrap-icons'; interface ImageComponentProps { name: string; parentPath: string; readOnly: boolean; docString: string; addNotification: (string) => void; // Define your component specific props here value: string; format: string; } export const ImageComponent = React.memo((props: ImageComponentProps) => { const { name, parentPath, value, docString, format, addNotification } = props; const renderCount = useRef(0); const [open, setOpen] = useState(true); // add this if you want to expand/collapse your component useEffect(() => { renderCount.current++; }); // This will trigger a notification if notifications are enabled. useEffect(() => { addNotification(`${parentPath}.${name} changed to ${value}.`); }, [props.value]); // Your component logic here return (
Render count: {renderCount.current}
)}{name}