tango-frame
tango-frame automatically exposes an ordinary Python device-control class as a TANGO Device Server and provides a
corresponding Python client wrapper.
The device-control class itself does not need to import PyTango or inherit from tango.server.Device. Public methods
are mapped to TANGO commands and Python properties are mapped to TANGO attributes.
Concept
The framework keeps the device implementation and the communication layer separate.
ordinary Python device class
|
+---- tango-frame ----> TANGO Device Server
| method -> command
| property -> attribute
|
+---- tango-frame ----> Python client wrapper
|
+---- TANGO DeviceProxy
tango-frame is not a separate RPC protocol layered on top of TANGO. It generates and uses normal TANGO commands and
attributes.
Features
- An ordinary Python class is used as the device API definition.
- Public methods are exposed as named TANGO commands.
@propertyvalues are exposed as TANGO attributes.- Read/write Python properties become read/write TANGO attributes.
- Native TANGO-compatible scalar and homogeneous array types are used when possible.
- Calls that cannot be represented directly by one TANGO command input, such as heterogeneous multiple arguments, are packed into one JSON string input.
- Complex return values that cannot be represented by a native TANGO type are encoded as JSON strings.
- The client presents the device API as Python methods and properties while communicating through
tango.DeviceProxy. - Device-specific client behavior can override automatic remote dispatch.
Package layout
src/
└── tango_frame/
├── __init__.py
├── auto_dispatch.py
├── client.py
├── codec.py
├── introspection.py
├── server.py
└── type_mapping.py
example/
└── device_server/
├── src/
│ └── device/
│ ├── device_class.py
│ └── server_tango/
│ ├── tango_ds.py
│ └── device_client.py
└── example/
├── server_start.py
└── client.py
src/tango_frame/ is the reusable framework and contains no concrete device implementation.
example/device_server/src/device/ is a concrete example device module. device_class.py is independent of TANGO.
server_tango/ contains only the TANGO-specific adaptation for that device.
Installation
pip install .
Important: strong Python / TANGO version dependencies. This is not a general-purpose Python RPC framework. It depends on PyTango and native TANGO libraries. Installation or execution may be impossible for certain combinations of Python version, operating system, CPU architecture, and TANGO runtime. Many existing instrument environments cannot use this framework, especially when their Python or TANGO versions cannot be upgraded.
Package and runtime requirements (v0.1.4)
| Component | Requirements and limitations |
|---|---|
| tango-frame | Package metadata requires Python >=3.9. Python 3.7/3.8 cannot use PyTango 10. |
| PyTango | pytango>=10. Version 10.0.x requires Python >=3.9; the 10.1 and 10.3 series require Python >=3.10. |
| xlogger-py | xlogger-py. Published version 0.3.1 requires Python >=3.7. |
| setuptools | setuptools>=61 for building; not a runtime dependency. |
| cppTango | PyTango 10.0.x requires cppTango >=10.0.0; other PyTango series may require different cppTango versions. |
| Native libraries | cppTango depends on libraries including omniORB4 and libzmq. Wheel availability, OS, architecture, and system libraries can prevent installation. |
Note: requires-python >=3.9 does not guarantee that every Python version >=3.9 works with every PyTango release.
Python 3.9 requires selecting an appropriate PyTango 10.0.x release, not the 10.3 series. Python 3.7-compatible type
annotations in a device class do not make the complete framework Python 3.7-compatible. Successful dependency resolution
also does not replace integration testing on the target device and TANGO deployment.
Official references: PyTango 10.0.0, PyTango 10.3.1, xlogger-py 0.3.1.
Device class
A device class remains an ordinary Python class.
from typing import List
class DeviceClass(object):
def __init__(self) -> None:
self._position = 0.0
def add(self, value: float) -> float:
self._position += value
return self._position
def move(
self,
position: float,
speed: float = 1.0,
wait: bool = True,
) -> bool:
self._position = position
return wait
def get_samples(self) -> List[float]:
return [1.0, 2.0, 3.0]
@property
def position(self) -> float:
return self._position
@position.setter
def position(self, value: float) -> None:
self._position = value
The class can still be instantiated and used directly without TANGO.
TANGO Device Server
A device-specific TANGO server class only binds the device class to AutoDispatchDevice.
from tango_frame import AutoDispatchDevice
from device.device_class import DeviceClass
class TangoDevice(AutoDispatchDevice):
device_class = DeviceClass
The public API is converted automatically. For the example above:
add(value: float) -> float
TANGO command: float -> float
move(position: float, speed: float, wait: bool) -> bool
TANGO command: JSON string -> bool
get_samples() -> List[float]
TANGO command: void -> homogeneous float array
position: float
TANGO read/write attribute
A device-specific server can exclude methods that must not be executed remotely:
class TangoDevice(AutoDispatchDevice):
device_class = DeviceClass
excluded_commands = {"save"}
The actual server is started with the normal PyTango server runtime. The example uses -nodb only to run locally
without a TANGO database.
Client
The corresponding client is defined by binding the same device class to AutoDispatchClient.
from tango_frame import AutoDispatchClient
from device.device_class import DeviceClass
class DeviceClient(AutoDispatchClient):
device_class = DeviceClass
The client uses TANGO DeviceProxy internally, while the caller uses Python-style methods and properties.
device = DeviceClient(
"tango://127.0.0.1:10000/test/tango/device#dbase=no"
)
print(device.position)
print(device.add(2.5))
device.position = 20.0
Device-specific client operations can be implemented normally. For example, a save(path) method should write on the
client machine rather than on the Device Server machine:
from pathlib import Path
class DeviceClient(AutoDispatchClient):
device_class = DeviceClass
def save(self, path: str) -> str:
data = self.get_data()
Path(path).write_bytes(data)
return path
The explicitly implemented client method takes precedence over automatic remote dispatch.
Type conversion
tango-frame prefers native TANGO values.
| Python API | TANGO representation |
|---|---|
bool |
native boolean |
int |
native integer |
float |
native double |
str |
native string |
homogeneous List[T] |
native TANGO array/spectrum |
| no argument / no return | void |
| multiple or heterogeneous arguments | one JSON string input |
| unsupported complex return value | JSON string |
JSON is therefore a fallback for Python call structures that do not map directly to TANGO's command model; it is not the default transport format.
Documentation
Sphinx/MyST documentation is under docs/.
cd docs
./build.sh
Version
Current version: 0.1.4
See CHANGELOG.md.
Metadata
Release files for tango-frame 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tango_frame-0.1.5.tar.gz | 17.9 kB | With provenance | Details for tango_frame-0.1.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tango_frame-0.1.5-py3-none-any.whl | Python 3 | none | any | Details for tango_frame-0.1.5-py3-none-any.whl |
Total release size: 35.6 kB
Release files / tango_frame-0.1.5.tar.gz
| Download URL | tango_frame-0.1.5.tar.gz |
|---|---|
| Size | 17.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0c4d3e202690c3656a08e2c601439b3ce1159b7e894c317b9525abadebc295d6
|
|
BLAKE2b-256 checksum How to use checksums |
932e86d29274a063d8110617a5488c212263a71192993f3ff78174f1b9582801
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / tango_frame-0.1.5-py3-none-any.whl
| Download URL | tango_frame-0.1.5-py3-none-any.whl |
|---|---|
| Size | 17.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c89ed7583950101493417d6e767971ccb7ddca68d02e1e2da9c676140acbba4
|
|
BLAKE2b-256 checksum How to use checksums |
a1a9be60737a7470480ac4f113bc199d1550a74c2c13d93ece98288cf516f594
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for tango-frame 0.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for tango_frame-0.1.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.