Setup
Get your development environment ready to build neops function blocks.
Prerequisites
You need four things installed:
| Tool | Version | Purpose |
|---|---|---|
| Python | 3.12+ | Runtime for function blocks |
| uv | latest | Fast Python package manager |
| Git | any recent | Clone your starting point (boilerplate or SDK repo) |
| Code editor | any | VS Code or PyCharm recommended |
Installing uv
See docs.astral.sh/uv for other installation methods.
Choose Your Path
There are two distinct ways to set up, depending on what you are here to do:
| You want to… | Your path |
|---|---|
| Build your own function blocks and run them in a worker | Create your own worker — most readers start here |
| Fix or extend the SDK itself and send a pull request | Working on the SDK itself |
Create Your Own Worker (recommended)
The recommended way to start is the
neops-worker-boilerplate
repository — a complete, ready-to-run worker project built on the published
neops_worker_sdk package. It ships example function blocks, a test suite, a
workflow definition, a Dockerfile, CI, and editor debug configurations, so you
start from a project that already lints, type-checks, and tests green.
Clone it under the name of your project:
uv sync installs everything, development tools included — no extra flags needed.
Make It Yours
The Python package my_worker/, the project name my-worker, and the function
block package fb.my_worker.example.io are deliberate placeholders. Rename them
all in one step:
The script requires a clean git tree, renames my_worker/ to acme_worker/,
rewrites every my_worker / my-worker occurrence across tracked files, and
regenerates uv.lock. Everything is left uncommitted for you to review with
git diff, then verify and commit:
It refuses to run twice — once the placeholder is gone, the repo counts as initialized.
Underscores, not hyphens
NAME must be a lowercase Python identifier (acme_worker, not
acme-worker). Function blocks are addressed as
<package>/<name>:<version>, and the workflow schema forbids hyphens in
every segment of that identifier — so the name that ends up in your FB
package must stay hyphen-free.
After the rename, the FB package is fb.acme_worker.example.io — change
example.io to your own domain by hand; the neops convention is a DNS-style
namespace like fb.base.neops.io. It appears in several files (the function
block modules, the workflow YAML in two places, and a test), so sweep with
grep -r example.io and replace every hit — workflows pin FB identifiers
exactly, and a partially renamed package surfaces only at runtime as
“Function block not found”.
What You Get
acme_worker/fb/ example function blocks (hello_world, device_interface_report, connection_test)
workflows/ a workflow definition ready to publish to the engine
tests/ FB tests (pytest, asyncio_mode=auto)
.env.dist environment template — cp to .env
Dockerfile runtime image; `make build-docker` builds it exactly as CI does
.github/workflows/ CI: lint, audit, type check, test, docker build
.vscode/ debug configurations for the worker and the tests
The example function blocks are graduated: hello_world.py is the smallest
possible FB, device_interface_report.py reads entity data without opening a
connection, and connection_test.py connects to a real device. Read them in
that order as you work through the next pages.
Prefer starting from scratch?
You can also scaffold a minimal project by hand with uv:
Replace the generated pyproject.toml with a configuration tailored for
neops development:
[project]
name = "my-neops-function-blocks"
version = "0.1.0"
description = "My first neops function blocks"
requires-python = ">=3.12"
dependencies = [
"neops_worker_sdk",
]
[project.optional-dependencies]
test = [
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",
"neops-remote-lab>=1.3.0",
]
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP", "B"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
This gives you:
- The
neops_worker_sdkruntime dependency - Test dependencies (
pytest,pytest-asyncio,neops-remote-lab) as optional extras - Sensible
ruffandpytestconfiguration
Install everything, including test dependencies:
If you prefer pip over uv, create a virtual environment and install manually:
Working on the SDK Itself
The neops-worker-sdk-py
repository is primarily maintained internally, but pull requests are welcome.
If you want to fix a bug or extend the SDK — rather than build function blocks
on top of it — clone the repository itself and set up its development
environment:
git clone https://github.com/zebbra/neops-worker-sdk-py.git
cd neops-worker-sdk-py
uv sync --extra test # install all dependencies (dev + fb groups are defaults)
make lint test # verify your environment works
The repository’s own README.md and AGENTS.md are the authoritative
contributor references — they document the project structure, conventions, and
the full verification gate to run before pushing
(uv run pytest -q && make lint && make typeCheck-baseline && make audit). The rest of this
guide assumes the worker path above, but the concepts apply either way.
Tooling
The boilerplate ships both tools below as development dependencies, wired to
make lint and make typeCheck. If you scaffolded manually, add them as shown.
Linting & Formatting — Ruff
We use Ruff for linting and formatting. It is a single, extremely fast tool (written in Rust) that replaces flake8, isort, and Black.
If it is not in your project yet, add it as a development dependency so every contributor uses the same version:
Then run it from your project:
Type Checking — Pyrefly
We use Pyrefly (by Meta) for type checking. It is a Rust-based type checker and language server that provides fast, accurate analysis for modern Python.
If needed, add it as a development dependency:
Then run it:
Why typed Python matters
neops function blocks rely heavily on type annotations. Here is why that matters:
- Auto-completion — your editor understands parameter and return types
- Early bug detection — type checkers catch mismatches before runtime
- Schema generation — Pydantic models (used for parameters and results) derive JSON schemas from type hints automatically
Compare:
vs.
async def run(self, params: EchoParams, context: WorkflowContext) -> FunctionBlockResult[EchoResult]:
...
The typed version gives your editor, your tests, and the neops platform everything they need to validate your code before it ever touches a device.
IDE Recommendations
The boilerplate already includes .vscode/settings.json (interpreter and
pytest wiring) and .vscode/launch.json with two debug configurations:
one that runs the live worker against your .env, and one for debugging
tests.
Recommended extensions:
| Extension | ID | Purpose |
|---|---|---|
| Python | ms-python.python |
Core Python support |
| Pyrefly | meta.pyrefly |
Type checking, auto-completion, go-to-definition |
| Ruff | charliermarsh.ruff |
Fast linting and formatting |
Pyrefly acts as a full language server — it provides inline type errors, hover information, and code navigation out of the box. It replaces Pylance for type analysis, so you only need one of the two.
Recommended settings (add to .vscode/settings.json):
Alternative: Pylance
If you prefer Microsoft’s Pylance
(ms-python.vscode-pylance), it works well too — add
"python.analysis.typeCheckingMode": "basic" to your settings.
Pyrefly disables Pylance by default when both are installed; keep only one
active to avoid duplicate diagnostics.
PyCharm has excellent built-in Python support — type checking, refactoring, and debugging work out of the box with fewer plugins.
Recommended plugins:
| Plugin | Purpose |
|---|---|
| Pydantic | Enhanced support for Pydantic models (auto-completion, validation) |
Configuration tips:
- Ruff integration: Go to Settings > Tools > External Tools, add a new
tool with program
uv, argumentsrun ruff check $FilePath$, and working directory$ProjectFileDir$. Add a second entry with argumentsrun ruff format $FilePath$for formatting. Assign keyboard shortcuts or configure file watchers to run on save. - Pyrefly for type checking: Add an external tool with program
uv, argumentsrun pyrefly check $FilePath$, and working directory$ProjectFileDir$. This gives you the same type analysis as CI, beyond PyCharm’s built-in inspector.
Environment Configuration
neops workers discover the platform and function blocks through environment variables. The boilerplate ships a documented template — copy it and adjust:
The two variables that matter first:
| Variable | Description |
|---|---|
URL_BLACKBOARD |
URL of your neops instance’s blackboard API |
DIR_FUNCTION_BLOCKS |
Directory where the worker discovers your function block modules (acme_worker/fb after the rename) |
If you scaffolded manually, create a .env file in your project root instead:
Tip
During local development with the test framework, these variables are not required – the test harness provides its own context.
Next: Write your first function block

