uhd: python: Make DeviceAddr behave like dict

This adds methods to the Python-bound version of device_addr_t so it
behaves like and can be used like a Python dictionary.

Specifically, it adds (including unit tests; assume a and b are of type
uhd.types.DeviceAddr):

```py
len(a)
a.keys()
a.values()
a.get('key')
a.get('key', 'default')
a.pop('key')
'key' in a
a['key']
a['key'] = 'new_val'
a == b
a != b
    # These differ from Python's versions:
a.update(b)
a.update({'foo': 'bar'})
```

The update() method follows the signature of
uhd::device_addr_t::update(), not that of Python's dict.update(), see
the manual for details (the fail_on_conflict argument). All other
methods behave equivalently between Python's dict and uhd::dict.

Signed-off-by: Martin Braun <martin.braun@ettus.com>
This commit is contained in:
Martin Braun 2022-10-24 16:10:38 +02:00 committed by skooNI
parent f14ce015b9
commit 18d11d2ef8
3 changed files with 134 additions and 3 deletions

View File

@ -9,6 +9,7 @@
#define INCLUDED_UHD_TYPES_PYTHON_HPP
#include <uhd/types/device_addr.hpp>
#include <uhd/types/dict.hpp>
#include <uhd/types/stream_cmd.hpp>
#include <pybind11/stl.h>
#include <map>
@ -44,9 +45,82 @@ void export_types(py::module& m)
.def("__str__", &uhd::device_addr_t::to_pp_string)
.def("to_string", &uhd::device_addr_t::to_string)
.def("to_pp_string", &uhd::device_addr_t::to_pp_string)
.def("to_dict", [](uhd::device_addr_t& self) {
return static_cast<std::map<std::string, std::string>>(self);
});
.def("to_dict",
[](uhd::device_addr_t& self) {
return static_cast<std::map<std::string, std::string>>(self);
})
.def("keys", [](const uhd::device_addr_t& self) { return self.keys(); })
.def("values", [](const uhd::device_addr_t& self) { return self.vals(); })
.def("get",
[](const uhd::device_addr_t& self, const std::string& key) -> py::object {
if (self.has_key(key)) {
return py::str(self.get(key));
}
return py::none();
})
.def("get",
[](const uhd::device_addr_t& self,
const std::string& key,
const std::string& other) { return self.get(key, other); })
.def(
"pop",
[](uhd::device_addr_t& self,
const std::string& key,
const char* def) -> std::string {
if (!self.has_key(key)) {
if (def == nullptr) {
throw py::key_error(key);
}
return std::string(def);
}
return self.pop(key);
},
py::arg("k"),
py::arg("d") = nullptr)
.def(
"update",
[](uhd::device_addr_t& self,
const uhd::device_addr_t& new_dict,
const bool fail_on_conflict) { self.update(new_dict, fail_on_conflict); },
py::arg("E"),
py::arg("fail_on_conflict") = true)
.def(
"update",
[](uhd::device_addr_t& self,
const std::map<std::string, std::string>& new_dict,
const bool fail_on_conflict) {
self.update(uhd::device_addr_t(new_dict), fail_on_conflict);
},
py::arg("E"),
py::arg("fail_on_conflict") = true)
.def("separate",
[](const uhd::device_addr_t& self) {
return uhd::separate_device_addr(self);
})
.def("__getitem__",
[](const uhd::device_addr_t& self, const std::string& key) -> std::string {
if (!self.has_key(key)) {
throw py::key_error(key);
}
return self.get(key);
})
.def("__setitem__",
[](uhd::device_addr_t& self, const std::string& key, const std::string& val) {
self.set(key, val);
})
.def("__contains__",
[](const uhd::device_addr_t& self, const std::string& key) {
return self.has_key(key);
})
.def("__len__", [](const uhd::device_addr_t& self) { return self.size(); })
.def("__eq__",
[](const uhd::device_addr_t& self, const uhd::device_addr_t& other) {
return self == other;
})
.def("__ne__",
[](const uhd::device_addr_t& self, const uhd::device_addr_t& other) {
return self != other;
});
// This will allow functions in Python that take a device_addr to also take
// a string:
py::implicitly_convertible<std::string, uhd::device_addr_t>();

View File

@ -72,6 +72,7 @@ set(pytest_sources
verify_fbs_test.py
pychdr_parse_test.py
uhd_image_downloader_test.py
device_addr_test.py
)
#turn each test cpp file into an executable with an int main() function

View File

@ -0,0 +1,56 @@
#
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Unit test for uhd.types.DeviceAddr
"""
import unittest
from uhd.types import DeviceAddr, separate_device_addr, combine_device_addrs
class DeviceAddrTest(unittest.TestCase):
""" Test Python-wrapped device_addr_t """
def test_device_addr_dict(self):
"""
Check we can use DeviceAddr like a Python dict
"""
dev_a = DeviceAddr("a=1,b=2,c=3")
self.assertEqual(len(dev_a), 3)
self.assertEqual(dev_a.to_dict(), {'a': '1', 'b': '2', 'c': '3'})
dev_d = dev_a.to_dict()
self.assertEqual(dev_a.keys(), list(dev_d.keys()))
self.assertEqual(dev_a.values(), list(dev_d.values()))
self.assertEqual(dev_a.get('a'), '1')
self.assertEqual(dev_a.get('a', 'boo'), '1')
self.assertEqual(dev_a.get('z'), None)
self.assertEqual(dev_a.get('z', 'boo'), 'boo')
self.assertEqual(dev_a['a'], '1')
with self.assertRaises(KeyError):
print(dev_a['z'])
self.assertTrue('a' in dev_a)
self.assertEqual(dev_a.pop('a'), '1')
self.assertEqual(len(dev_a), 2)
self.assertTrue('a' not in dev_a)
self.assertEqual(dev_a.pop('z', 'boo'), 'boo')
with self.assertRaises(KeyError):
print(dev_a.pop('z'))
dev_a.update({'a': '9'})
self.assertEqual(dev_a.get('a'), '9')
with self.assertRaises(RuntimeError):
dev_a.update({'a': '12'})
dev_a.update({'a': '12'}, False)
self.assertEqual(dev_a.get('a'), '12')
def test_device_addr_separate(self):
dev_a = DeviceAddr("addr0=192.168.40.1,addr1=192.168.40.2,time_source=external")
self.assertEqual(len(dev_a.separate()), 2)
self.assertEqual(len(dev_a.separate()[0]), 2)
self.assertEqual(dev_a.separate(), separate_device_addr(dev_a))
self.assertEqual(
DeviceAddr(
'addr0=192.168.40.1,time_source0=external,'
'addr1=192.168.40.2,time_source1=external'),
combine_device_addrs(dev_a.separate()))