Catalog PyPI
Back to packageRELEASE DETAILS

1.2.0

PyPI

AFL-automation

Hardware interfacing code for the Autonomous Formulation Lab

Published10 Dec 2025
Direct dependencies62
Transitive dependenciesNot calculated

README

Version 1.2.0 documentation

NIST Autonomous Formulation Laboratory - Automation Software

This package contains the core laboratory automation software used in the NIST AFL platform.

Its core is the 'DeviceServer' API, a simple way of exposing functionality in simple Python classes to the outside world via HTTP servers. It includes robust item queueing support, output rendering, and hooks to allow for 'smart' generation of user interfaces automatically.

Specific deviceserver instances are provided for a variety of hardware used in the AFL platform: syringe pumps, valves, multiposition flow selectors, UV-Vis spectrometers, x-ray and neutron scattering instruments/beamlines. There are further deviceserver classes that integrate these base devices to perform higher-level functions, e.g. "loading". These classes aim to specify instructions for running a particular protocol in a hardware-agnostic way.

Virtual servers for offline testing

Several stub modules are provided so you can launch the AFL stack without any physical hardware. Running one of these modules the first time creates ~/.afl/config.json containing the Tiled server URL, API key and port bindings. Update this file as needed.

Example commands to start the dummy services:

# start a dummy OT2 robot
python -m AFL.automation.prepare.Dummy_OT2_Driver

# start a virtual sample loader
python server_scripts/virtual_instrument/DummyLoader.py

# optional virtual detectors
python server_scripts/virtual_instrument/VirtualSANS_data.py
python server_scripts/virtual_instrument/VirtualSpec_Data.py

For a full offline demo you can start everything at once:

python server_scripts/virtual_instrument/AllDummy.py

These stub servers expose the same HTTP API as the real hardware allowing you to test an AFL workflow entirely in software.

Production deployment

By default the APIServer will use the waitress WSGI server if it is installed. To fall back to Flask's built-in server pass --no-waitress to AFL.automation.shared.launcher.

Running tests

This repository uses pytest for unit tests. A GitHub Actions workflow runs the tests automatically on every push and pull request using .github/workflows/test.yaml. The workflow installs the package along with the dependencies listed in pyproject.toml.

To execute the tests locally, install the package in editable mode and run pytest. If the installation fails because the package version cannot be determined from Git tags, set SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 as in the CI workflow:

SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 pip install -e .
pytest

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 62

Native requirements
62 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
bokeh
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
bokehruntime
caproto
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
caprotoruntime
entrypoints
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
entrypointsruntime
flask
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
flaskruntime
flask-cors
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
flask-corsruntime
flask-jwt-extended
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
flask-jwt-extended>=4.0runtime
h5py
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
h5pyruntime
ipython
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
ipythonruntime
ipywidgets
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
ipywidgetsruntime
lazy-loader
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
lazy-loaderruntime
matplotlib
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
matplotlibruntime
numpy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
numpyruntime
pandas
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pandasruntime
periodictable
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
periodictableruntime
pillow
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pillowruntime
pint
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pintruntime
plotly
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
plotlyruntime
pyparsing
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pyparsingruntime
pytest
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pytestruntime
requests
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
requestsruntime
scikit-image
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
scikit-imageruntime
scikit-learn
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
scikit-learnruntime
scipy
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
scipyruntime
setuptools
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
setuptoolsruntime
six
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sixruntime
tiled
Environment
{
  "extras": "all",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
tiled[all]runtime
tqdm
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
tqdmruntime
waitress
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
waitressruntime
werkzeug
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
werkzeugruntime
xarray
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
xarrayruntime
zeroconf
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
zeroconfruntime
myst-parser
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
myst-parser>=2.0.0; extra == "docs"runtime
nbsphinx
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
nbsphinx>=0.9.3; extra == "docs"runtime
pydata-sphinx-theme
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pydata-sphinx-theme; extra == "docs"runtime
sphinx-autodoc-typehints
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinx-autodoc-typehints>=1.25.2; extra == "docs"runtime
sphinx-autosummary-accessors
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinx-autosummary-accessors; extra == "docs"runtime
sphinx-copybutton
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinx-copybutton>=0.5.2; extra == "docs"runtime
sphinx-rtd-theme
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinx-rtd-theme>=2.0.0; extra == "docs"runtime
sphinx
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinx>=7.1.0; extra == "docs"runtime
sphinxcontrib-apidoc
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinxcontrib-apidoc; extra == "docs"runtime
sphinxcontrib-napoleon
Environment
{
  "extras": "",
  "marker": "extra == \"docs\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sphinxcontrib-napoleon; extra == "docs"runtime
alphashape
Environment
{
  "extras": "",
  "marker": "extra == \"geometry\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
alphashape; extra == "geometry"runtime
shapely
Environment
{
  "extras": "",
  "marker": "extra == \"geometry\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
shapely; extra == "geometry"runtime
labjack-ljm
Environment
{
  "extras": "",
  "marker": "extra == \"labjack\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
labjack-ljm; extra == "labjack"runtime
gpflow
Environment
{
  "extras": "",
  "marker": "extra == \"ml\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
gpflow; extra == "ml"runtime
tensorflow
Environment
{
  "extras": "",
  "marker": "extra == \"ml\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
tensorflow; extra == "ml"runtime
epics
Environment
{
  "extras": "",
  "marker": "extra == \"neutron-scattering\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
epics; extra == "neutron-scattering"runtime
mantid
Environment
{
  "extras": "",
  "marker": "extra == \"neutron-scattering\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
mantid; extra == "neutron-scattering"runtime
sans
Environment
{
  "extras": "",
  "marker": "extra == \"neutron-scattering\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sans; extra == "neutron-scattering"runtime
opentrons
Environment
{
  "extras": "",
  "marker": "extra == \"opentrons\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
opentrons; extra == "opentrons"runtime
piplates
Environment
{
  "extras": "",
  "marker": "extra == \"piplates\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
piplates; extra == "piplates"runtime
certif-pyspec
Environment
{
  "extras": "",
  "marker": "extra == \"pyspec\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
certif-pyspec; extra == "pyspec"runtime
paramiko
Environment
{
  "extras": "",
  "marker": "extra == \"remote-access\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
paramiko; extra == "remote-access"runtime
rpi-gpio
Environment
{
  "extras": "",
  "marker": "extra == \"rpi-gpio\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
rpi-gpio; extra == "rpi-gpio"runtime
sasdata
Environment
{
  "extras": "",
  "marker": "extra == \"sas-analysis\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sasdata; extra == "sas-analysis"runtime
sasmodels
Environment
{
  "extras": "",
  "marker": "extra == \"sas-analysis\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
sasmodels; extra == "sas-analysis"runtime
fabio
Environment
{
  "extras": "",
  "marker": "extra == \"scattering-processing\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
fabio; extra == "scattering-processing"runtime
pyfai
Environment
{
  "extras": "",
  "marker": "extra == \"scattering-processing\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pyfai; extra == "scattering-processing"runtime
seabreeze
Environment
{
  "extras": "",
  "marker": "extra == \"seabreeze\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
seabreeze; extra == "seabreeze"runtime
pyserial
Environment
{
  "extras": "",
  "marker": "extra == \"serial\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pyserial; extra == "serial"runtime
opencv-python
Environment
{
  "extras": "",
  "marker": "extra == \"vision\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
opencv-python; extra == "vision"runtime
pywin32
Environment
{
  "extras": "",
  "marker": "extra == \"win32\"",
  "requires_python": ">=3.8",
  "evaluation": "NOT_EVALUATED"
}
pywin32; extra == "win32"runtime

Distribution files

2 collected
afl_automation-1.2.0-py3-none-any.whl

bdist_wheel · 19,138.86 KB · Python py3

Source file
afl_automation-1.2.0.tar.gz

sdist · 18,494.17 KB · Python source

Source file

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 21:08 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:497d1a76fdfd3af140d18aad3fdfba8e44023027ff06e144a01be6620199f10d

Other collected versions

View all collected versions →