Change pprint strings to be prettier with nested structs

Nested types used to end up printed in a single line which is difficult to read. For improved readability, the following changes:

  • Align the left edges instead of the equal signs
  • Use double quotes for strings instead of single quotes
  • Indent nested structures, and print over multiple lines
  • Put closing bracket on a new line
  • Add support for plain Python dicts, like we have in DeviceInfo
  • Improve DevError and DevFailed, including removing extra whitespace before and after desc and origin fields
  • Update GroupReply, GroupAttrReply and GroupCmdReply string representations to actually show data/error messages
  • Changes only affects str formatting, not repr. repr is still just a single line.

Overall, this makes the formatting a bit more like the output of the black autoformatter.

Examples below.

DeviceInfo

Old:

DeviceInfo[
     dev_class = 'PowerSupply'
      dev_type = 'PowerSupply'
       doc_url = 'Doc URL = http://www.tango-controls.org'
   server_host = 'host.domain'
     server_id = 'PowerSupply/test'
server_version = 6
  version_info = {'Build.PyTango.Boost': '1.87.0', 'Build.PyTango.NumPy': '2.1.3', 'Build.PyTango.Python': '3.13.0', 'Build.PyTango.cppTango': '10.0.2', 'NumPy': '2.1.3', 'PyTango': '10.1.0.dev0', 'Python': '3.13.0', 'cppTango': '10.0.2', 'cppTango.git_revision': 'unknown', 'cppzmq': '41000', 'idl': '6.0.2', 'omniORB': '4.3.2', 'opentelemetry-cpp': '1.18.0', 'zmq': '40305'}]

New:

DeviceInfo[
    dev_class = "PowerSupply"
    dev_type = "PowerSupply"
    doc_url = "Doc URL = http://www.tango-controls.org"
    server_host = "host.domain"
    server_id = "PowerSupply/test"
    server_version = 6
    version_info = {
        "Build.PyTango.Boost": "1.87.0",
        "Build.PyTango.NumPy": "2.1.3",
        "Build.PyTango.Python": "3.13.0",
        "Build.PyTango.cppTango": "10.0.2",
        "NumPy": "2.1.3",
        "PyTango": "10.1.0.dev0",
        "Python": "3.13.0",
        "cppTango": "10.0.2",
        "cppTango.git_revision": "unknown",
        "cppzmq": "41000",
        "idl": "6.0.2",
        "omniORB": "4.3.2",
        "opentelemetry-cpp": "1.18.0",
        "zmq": "40305"
    }
]
AttributeInfoEx

Old:

AttributeInfoEx[
            alarms = AttributeAlarmInfo(delta_t = '', delta_val = '', extensions = [], max_alarm = '', max_warning = '', min_alarm = '', min_warning = '')
       data_format = tango._tango.AttrDataFormat.SCALAR
         data_type = tango._tango.CmdArgType.DevVoid
       description = ''
        disp_level = tango._tango.DispLevel.OPERATOR
      display_unit = ''
       enum_labels = []
            events = AttributeEventInfo(arch_event = ArchiveEventInfo(archive_abs_change = '', archive_period = '', archive_rel_change = '', extensions = []), ch_event = ChangeEventInfo(abs_change = '', extensions = [], rel_change = ''), per_event = PeriodicEventInfo(extensions = [], period = ''))
        extensions = []
            format = ''
             label = ''
         max_alarm = ''
         max_dim_x = 0
         max_dim_y = 0
         max_value = ''
         memorized = tango._tango.AttrMemorizedType.NOT_KNOWN
         min_alarm = ''
         min_value = ''
              name = ''
    root_attr_name = ''
     standard_unit = ''
    sys_extensions = []
              unit = ''
          writable = tango._tango.AttrWriteType.READ
writable_attr_name = '']

New:

AttributeInfoEx[
    alarms = AttributeAlarmInfo[
        delta_t = ""
        delta_val = ""
        extensions = []
        max_alarm = ""
        max_warning = ""
        min_alarm = ""
        min_warning = ""
    ]
    data_format = tango._tango.AttrDataFormat.SCALAR
    data_type = tango._tango.CmdArgType.DevVoid
    description = ""
    disp_level = tango._tango.DispLevel.OPERATOR
    display_unit = ""
    enum_labels = []
    events = AttributeEventInfo[
        arch_event = ArchiveEventInfo[
            archive_abs_change = ""
            archive_period = ""
            archive_rel_change = ""
            extensions = []
        ]
        ch_event = ChangeEventInfo[
            abs_change = ""
            extensions = []
            rel_change = ""
        ]
        per_event = PeriodicEventInfo[
            extensions = []
            period = ""
        ]
    ]
    extensions = []
    format = ""
    label = ""
    max_alarm = ""
    max_dim_x = 0
    max_dim_y = 0
    max_value = ""
    memorized = tango._tango.AttrMemorizedType.NOT_KNOWN
    min_alarm = ""
    min_value = ""
    name = ""
    root_attr_name = ""
    standard_unit = ""
    sys_extensions = []
    unit = ""
    writable = tango._tango.AttrWriteType.READ
    writable_attr_name = ""
]
DevFailed and DevError

Old:

>>> dp = tango.DeviceProxy("train/ps/1")
>>> dp.calibrate()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/pytango/tango/device_proxy.py", line 363, in f
    return dp.command_inout(name, *args, **kwds)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/green.py", line 226, in greener
    return executor.run(fn, args, kwargs, wait=wait, timeout=timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/green.py", line 116, in run
    return fn(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/connection.py", line 77, in __Connection__command_inout
    r = Connection.command_inout_raw(self, name, *args, **kwds)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/connection.py", line 111, in __Connection__command_inout_raw
    return self.__command_inout(cmd_name, param)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyTango.DevFailed: DevFailed[
DevError[
    desc = AssertionError: Oh no!

  origin = Traceback (most recent call last):
  File "/path/to/pytango/tango/server.py", line 1443, in wrapped_command_method
    return get_worker().execute(cmd_method, self, *args, **kwargs)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/green.py", line 113, in execute
    return fn(*args, **kwargs)
  File "/path/to/pytango/examples/training/server/ps0a.py", line 19, in calibrate
    assert 0, "Oh no!"
           ^
AssertionError: Oh no!

  reason = PyDs_PythonError
severity = ERR]

DevError[
    desc = Cannot execute command
  origin = virtual CORBA::Any *PyCmd::execute(Tango::DeviceImpl *, const CORBA::Any &) at (/path/to/pytango/ext/server/command.cpp:92)
  reason = PyDs_UnexpectedFailure
severity = ERR]

DevError[
    desc = Failed to execute command_inout on device train/ps/1, command calibrate
  origin = virtual DeviceData Tango::Connection::command_inout(const std::string &, const DeviceData &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:1432)
  reason = API_CommandFailed
severity = ERR]
]
>>>

New:

>>> dp.calibrate()
Traceback (most recent call last):
  File "<python-input-14>", line 1, in <module>
    dp.calibrate()
    ~~~~~~~~~~~~^^
  File "/path/to/pytango/tango/device_proxy.py", line 363, in f
    return dp.command_inout(name, *args, **kwds)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/green.py", line 226, in greener
    return executor.run(fn, args, kwargs, wait=wait, timeout=timeout)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/pytango/tango/green.py", line 116, in run
    return fn(*args, **kwargs)
  File "/path/to/pytango/tango/connection.py", line 77, in __Connection__command_inout
    r = Connection.command_inout_raw(self, name, *args, **kwds)
  File "/path/to/pytango/tango/connection.py", line 111, in __Connection__command_inout_raw
    return self.__command_inout(cmd_name, param)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
PyTango.DevFailed: DevFailed[
    DevError[
        desc = AssertionError: Oh no!
        origin = Traceback (most recent call last):
              File "/path/to/pytango/tango/server.py", line 1443, in wrapped_command_method
                return get_worker().execute(cmd_method, self, *args, **kwargs)
                       ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              File "/path/to/pytango/tango/green.py", line 113, in execute
                return fn(*args, **kwargs)
              File "/path/to/pytango/examples/training/server/ps0a.py", line 19, in calibrate
                assert 0, "Oh no!"
                       ^
            AssertionError: Oh no!
        reason = PyDs_PythonError
        severity = ERR
    ],
    DevError[
        desc = Cannot execute command
        origin = virtual CORBA::Any *PyCmd::execute(Tango::DeviceImpl *, const CORBA::Any &) at (/path/to/pytango/ext/server/command.cpp:92)
        reason = PyDs_UnexpectedFailure
        severity = ERR
    ],
    DevError[
        desc = Failed to execute command_inout on device train/ps/1, command calibrate
        origin = virtual DeviceData Tango::Connection::command_inout(const std::string &, const DeviceData &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:1432)
        reason = API_CommandFailed
        severity = ERR
    ]
]
>>>
Group replies

Old:

GroupCmdReply[
]

GroupAttrReply[
]

GroupReply[
]

New:

GroupCmdReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "State"
    enabled = True
    has_failed = False
    data = UNKNOWN
]

GroupAttrReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "attr"
    enabled = True
    has_failed = False
    data = DeviceAttribute[
        data_format = tango._tango.AttrDataFormat.SCALAR
        dim_x = 1
        dim_y = 0
        has_failed = False
        is_empty = False
        name = "attr"
        nb_read = 1
        nb_written = 1
        quality = tango._tango.AttrQuality.ATTR_VALID
        r_dimension = AttributeDimension[
            dim_x = 1
            dim_y = 0
        ]
        time = TimeVal(tv_nsec = 0, tv_sec = 1741759431, tv_usec = 455195)
        type = tango._tango.CmdArgType.DevDouble
        value = 12.3
        w_dim_x = 1
        w_dim_y = 0
        w_dimension = AttributeDimension[
            dim_x = 1
            dim_y = 0
        ]
        w_value = 0.0
    ]
]

GroupReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "attr"
    enabled = True
    has_failed = False
]

GroupCmdReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "cmd_fail"
    enabled = True
    has_failed = True
    err_stack = [
        DevError[
            desc = RuntimeError: Fail for test
            origin = Traceback (most recent call last):
                  File "/path/to/pytango/tango/utils.py", line 2507, in trace_wrapper
                    ret = fn(*args, **kwargs)
                  File "/path/to/pytango/tango/server.py", line 1796, in wrapped_command_method
                    return get_worker().execute(cmd_method, self, *args, **kwargs)
                           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  File "/path/to/pytango/tango/green.py", line 105, in execute
                    return fn(*args, **kwargs)
                  File "/path/to/pytango/tests/test_pprint.py", line 40, in cmd_fail
                    raise RuntimeError("Fail for test")
                RuntimeError: Fail for test
            reason = PyDs_PythonError
            severity = ERR
        ],
        DevError[
            desc = Cannot execute command
            origin = virtual CORBA::Any *PyCmd::execute(Tango::DeviceImpl *, const CORBA::Any &) at (/path/to/pytango/ext/server/command.cpp:316)
            reason = PyDs_UnexpectedFailure
            severity = ERR
        ],
        DevError[
            desc = Failed to execute command_inout_asynch on device test/device/1, command cmd_fail
            origin = virtual DeviceData Tango::Connection::command_inout_reply(long, long) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/proxy_asyn.cpp:699)
            reason = API_CommandFailed
            severity = ERR
        ]
    ]
]

GroupAttrReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "attr_fail"
    enabled = True
    has_failed = True
    err_stack = [
        DevError[
            desc = RuntimeError: Fail for test
            origin = Traceback (most recent call last):
                  File "/path/to/pytango/tango/utils.py", line 2507, in trace_wrapper
                    ret = fn(*args, **kwargs)
                  File "/path/to/pytango/tango/server.py", line 100, in read_attr
                    ret = worker.execute(read_method, self)
                  File "/path/to/pytango/tango/green.py", line 105, in execute
                    return fn(*args, **kwargs)
                  File "/path/to/pytango/tests/test_pprint.py", line 32, in attr_fail
                    raise RuntimeError("Fail for test")
                RuntimeError: Fail for test
            reason = PyDs_PythonError
            severity = ERR
        ],
        DevError[
            desc = Failed to read_attributes on device test/device/1, attribute attr_fail
            origin = virtual DeviceAttribute *Tango::DeviceProxy::read_attribute_reply(long, long) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/proxy_asyn.cpp:1744)
            reason = API_AttributeFailed
            severity = ERR
        ]
    ]
]

GroupReply[
    dev_name = "tango://127.0.0.1:61774/test/device/1#dbase=no"
    obj_name = "attr_fail"
    enabled = True
    has_failed = True
    err_stack = [
        DevError[
            desc = RuntimeError: Fail for test
            origin = Traceback (most recent call last):
                  File "/path/to/pytango/tango/utils.py", line 2507, in trace_wrapper
                    ret = fn(*args, **kwargs)
                  File "/path/to/pytango/tango/server.py", line 155, in write_attr
                    return get_worker().execute(write_method, self, value)
                           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  File "/path/to/pytango/tango/green.py", line 105, in execute
                    return fn(*args, **kwargs)
                  File "/path/to/pytango/tests/test_pprint.py", line 36, in attr_fail
                    raise RuntimeError("Fail for test")
                RuntimeError: Fail for test
            reason = PyDs_PythonError
            severity = ERR
        ],
        DevError[
            desc = Failed to execute write_attributes_asynch on device test/device/1
                Attribute(s): attr_fail
            origin = void Tango::DeviceProxy::write_attr_except(CORBA::Request_ptr, long, TgRequest::ReqType) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/proxy_asyn.cpp:2612)
            reason = API_AttributeFailed
            severity = ERR
        ]
    ]
]

Merge request reports

Loading