UniteLabs
Reference

Project Structure

Where code, configuration, errors, and tests go in a Python connector package.

Repository layout

Connectors follow a src/ directory structure.

├── device-name
  ├── CHANGELOG.md
  ├── README.md
  ├── pyproject.toml
  ├── uv.lock
  ├── scripts
    └── example.py
  ├── src
    └── unitelabs
      └── package_name
        ├── __init__.py
        ├── __main__.py
        ├── config.py
        └── io
          ├── __init__.py
          ├── errors.py
          └── protocol.py
        └── features
          └── random_number_provider
            ├── __init__.py
            └── random_number_provider.py
  └── tests
    ├── conftest.py
    ├── features
      └── test_random_number_provider.py
    └── io
      └── test_protocol.py
Some boilerplate files are left out. The tree shows the files needed for Python packaging and the overall structure of a connector project.

io/ talks to the device and has no knowledge of SiLA; features/ defines the SiLA interface and calls into io/. See Connector Architecture for why the layers are split this way.

For file and folder names, see Naming Conventions. For the tests/ layout, see Testing.

Split a large protocol

Once the protocol class gets too long, split it into delegates, one submodule per physical module of the device:

└── io
  ├── protocol.py
  └── commands
    ├── pipette
    ├── core_96
    └── heater_shaker

Name and document the commands in io/ the way the vendor's own documentation does, so the protocol layer stays traceable to the manual. Translating to the UniteLabs standard vocabulary happens in features/.

Configuration

Keep device-specific configuration in a dedicated config.py rather than inline in __init__.py. The entry point stays clean, and the config is easy to find and test on its own. See the Configuration Guide for the full details.

Don't commit config.json. It is a development-only file, created by config create or connector dev, and nothing keeps it in sync with config.py. Add it to .gitignore.

Custom exceptions

Put custom exception types for hardware-level errors in io/errors.py, close to the code that raises them. Exceptions specific to the SiLA interface can live in the feature file, or in features/errors.py when several features share them.

Debugging commands and standard feature extensions

Put debugging and developer-helper commands, such as jogging for calibration or sending raw commands, in their own feature. Don't mix them into the connector's functional features.

When your device needs a command or property that a standard SiLA feature doesn't define, add it directly to that feature class. Don't create a new feature for it or move it into the debugging feature.

Order within a file

Order a class from top to bottom:

  1. Constructor
  2. Properties
  3. Public methods
  4. Private methods

A first-time reader wants to know what the class does before its internal details. Give private methods self-descriptive names, so the file reads top to bottom without jumping around.

Feature classes shouldn't need private helpers at all: keep business logic in io/.

README and CHANGELOG

Every connector should have both:

  • README.md: what the connector does, which device models it supports and has been tested against, how to configure and run it, and any known limitations.
  • CHANGELOG.md: a record of every release, with what changed, what was fixed, and what was added. This is the first place someone looks when an upgrade breaks something.

Last updated