diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | bindings/python/lilv.py | 99 | ||||
-rw-r--r-- | bindings/test/python/test_api.py | 58 |
3 files changed, 113 insertions, 45 deletions
@@ -1,5 +1,6 @@ lilv (0.24.7) unstable; + * Allow passing strings for URIs in Python API when unambiguous * Fix cases where incorrect translation is used * Fix deleting state bundles loaded from the model * Fix memory leak when dyn-manifest has no plugins (thanks Michael Fisher) diff --git a/bindings/python/lilv.py b/bindings/python/lilv.py index 8e30791..8d3674e 100644 --- a/bindings/python/lilv.py +++ b/bindings/python/lilv.py @@ -43,15 +43,23 @@ def _is_string(obj): return isinstance(obj, basestring) -def _as_uri(obj): +def _as_uri(world, obj): """Utility function for converting some object into a URI node""" if type(obj) in [Plugin, PluginClass, UI]: return obj.get_uri() - else: - assert type(obj) == Node + + if _is_string(obj): + if len(obj) == 0: + raise ValueError("empty string given where URI is required") + + return world.new_uri(obj) + + if type(obj) == Node: assert obj.node return Node(obj.world, c.node_duplicate(obj.node)) + raise ValueError("%s given where URI is required" % type(obj)) + # LV2 types @@ -226,9 +234,8 @@ class Plugin(Structure): May return None if the property was not found, or if object(s) is not sensibly represented as a LilvNodes (e.g. blank nodes). """ - return Nodes( - self.world, c.plugin_get_value(self.plugin, predicate.node), True - ) + p = _as_uri(self.world, predicate) + return Nodes(self.world, c.plugin_get_value(self.plugin, p.node), True) def has_feature(self, feature_uri): """Return whether a feature is supported by a plugin. @@ -236,7 +243,8 @@ class Plugin(Structure): This will return true if the feature is an optional or required feature of the plugin. """ - return c.plugin_has_feature(self.plugin, feature_uri.node) + f = _as_uri(self.world, feature_uri) + return c.plugin_has_feature(self.plugin, f.node) def get_supported_features(self): """Get the LV2 Features supported (required or optionally) by a plugin. @@ -279,7 +287,8 @@ class Plugin(Structure): def has_extension_data(self, uri): """Return whether or not a plugin provides specific extension data.""" - return c.plugin_has_extension_data(self.plugin, uri.node) + u = _as_uri(self.world, uri) + return c.plugin_has_extension_data(self.plugin, u.node) def get_extension_data(self): """Get a sequence of all extension data provided by a plugin. @@ -297,8 +306,10 @@ class Plugin(Structure): def get_num_ports_of_class(self, *args): """Get the number of ports of some class(es) on this plugin.""" + classes = list(map(lambda n: _as_uri(self.world, n), args)) + c_nodes = list(map(lambda n: n.node, classes)) return c.plugin_get_num_ports_of_class( - self.plugin, *(list(map(lambda n: n.node, args)) + [None]) + self.plugin, *(c_nodes + [None]) ) def has_latency(self): @@ -363,11 +374,11 @@ class Plugin(Structure): input and output ports for a particular designation. If `port_class` is None, any port with the given designation will be returned. """ + pc = _as_uri(self.world, port_class) + d = _as_uri(self.world, designation) return Port.wrap( self, - c.plugin_get_port_by_designation( - self.plugin, port_class.node, designation.node - ), + c.plugin_get_port_by_designation(self.plugin, pc.node, d.node), ) def get_project(self): @@ -422,8 +433,9 @@ class Plugin(Structure): To actually load the data for each returned resource, use world.load_resource(). """ + t = _as_uri(self.world, resource_type) return Nodes( - self.world, c.plugin_get_related(self.plugin, resource_type), True + self.world, c.plugin_get_related(self.plugin, t.node), True ) def get_uis(self): @@ -506,9 +518,10 @@ class Port(Structure): def get_value(self, predicate): """Port analog of Plugin.get_value().""" + p = _as_uri(self.plugin.world, predicate) return Nodes( self.plugin.world, - c.port_get_value(self.plugin.plugin, self.port, predicate.node), + c.port_get_value(self.plugin.plugin, self.port, p.node), True, ) @@ -519,9 +532,10 @@ class Port(Structure): but is simpler to use in the common case of only caring about one value. The caller is responsible for freeing the returned node. """ + p = _as_uri(self.plugin.world, predicate) return Node.wrap( self.plugin.world, - c.port_get(self.plugin.plugin, self.port, predicate.node), + c.port_get(self.plugin.plugin, self.port, p.node), ) def get_properties(self): @@ -534,9 +548,8 @@ class Port(Structure): def has_property(self, property_uri): """Return whether a port has a certain property.""" - return c.port_has_property( - self.plugin.plugin, self.port, property_uri.node - ) + p = _as_uri(self.plugin.world, property_uri) + return c.port_has_property(self.plugin.plugin, self.port, p.node) def supports_event(self, event_type): """Return whether a port supports a certain event type. @@ -544,9 +557,8 @@ class Port(Structure): More precisely, this returns true iff the port has an atom:supports or an ev:supportsEvent property with `event_type` as the value. """ - return c.port_supports_event( - self.plugin.plugin, self.port, event_type.node - ) + t = _as_uri(self.plugin.world, event_type) + return c.port_supports_event(self.plugin.plugin, self.port, t.node) def get_index(self): """Get the index of a port. @@ -599,7 +611,8 @@ class Port(Structure): convenience, but this function is designed so that Lilv is usable with any port types without requiring explicit support in Lilv. """ - return c.port_is_a(self.plugin.plugin, self.port, port_class.node) + k = _as_uri(self.plugin.world, port_class) + return c.port_is_a(self.plugin.plugin, self.port, k.node) def get_range(self): """Return the default, minimum, and maximum values of a port as a tuple. @@ -697,7 +710,8 @@ class UI(Structure): def is_a(self, class_uri): """Check whether a plugin UI has a given type.""" - return c.ui_is_a(self.ui, class_uri.node) + u = _as_uri(self.world, class_uri) + return c.ui_is_a(self.ui, u.node) def get_bundle_uri(self): """Get the URI of the UI's bundle.""" @@ -953,7 +967,7 @@ class Plugins(Collection): self.world = world def __contains__(self, key): - return bool(self.get_by_uri(_as_uri(key))) + return bool(self.get_by_uri(_as_uri(self.world, key))) def __len__(self): return c.plugins_size(self.collection) @@ -969,11 +983,9 @@ class Plugins(Collection): return plugin def get_by_uri(self, uri): - if type(uri) == str: - uri = self.world.new_uri(uri) - + u = _as_uri(self.world, uri) return Plugin.wrap( - self.world, c.plugins_get_by_uri(self.collection, uri.node) + self.world, c.plugins_get_by_uri(self.collection, u.node) ) @@ -1000,7 +1012,7 @@ class PluginClasses(Collection): c.plugin_classes_free(self.collection) def __contains__(self, key): - return bool(self.get_by_uri(_as_uri(key))) + return bool(self.get_by_uri(_as_uri(self.world, key))) def __len__(self): return c.plugin_classes_size(self.collection) @@ -1016,10 +1028,9 @@ class PluginClasses(Collection): return klass def get_by_uri(self, uri): - if type(uri) == str: - uri = self.world.new_uri(uri) + u = _as_uri(self.world, uri) - plugin_class = c.plugin_classes_get_by_uri(self.collection, uri.node) + plugin_class = c.plugin_classes_get_by_uri(self.collection, u.node) return PluginClass(self.world, plugin_class) if plugin_class else None @@ -1050,7 +1061,7 @@ class UIs(Collection): c.uis_free(self.collection) def __contains__(self, uri): - return bool(self.get_by_uri(_as_uri(uri))) + return bool(self.get_by_uri(_as_uri(self.world, uri))) def __len__(self): return c.uis_size(self.collection) @@ -1066,10 +1077,8 @@ class UIs(Collection): return ui def get_by_uri(self, uri): - if type(uri) == str: - uri = self.world.new_uri(uri) - - ui = c.uis_get_by_uri(self.collection, uri.node) + u = _as_uri(self.world, uri) + ui = c.uis_get_by_uri(self.collection, u.node) return UI(self.world, ui) if ui else None @@ -1224,7 +1233,8 @@ class World(Structure): unchanged between (or even during) program invocations. Plugins (among other things) MUST be identified by URIs (not paths) in save files. """ - c.world_load_bundle(self.world, bundle_uri.node) + u = _as_uri(self, bundle_uri) + c.world_load_bundle(self.world, u.node) def load_specifications(self): """Load all specifications from currently loaded bundles. @@ -1252,18 +1262,19 @@ class World(Structure): have been separately loaded with load_resource(), they must be separately unloaded with unload_resource(). """ - return c.world_unload_bundle(self.world, bundle_uri.node) + u = _as_uri(self, bundle_uri) + return c.world_unload_bundle(self.world, u.node) def load_resource(self, resource): """Load all the data associated with the given `resource`. - The resource must be a subject (i.e. a URI or a blank node). + The resource parameter must be a URI. Returns the number of files parsed, or -1 on error. All accessible data files linked to `resource` with rdfs:seeAlso will be loaded into the world model. """ - uri = _as_uri(resource) + uri = _as_uri(self, resource) ret = c.world_load_resource(self.world, uri.node) return ret @@ -1275,7 +1286,7 @@ class World(Structure): This unloads all data loaded by a previous call to load_resource() with the given `resource`. """ - uri = _as_uri(resource) + uri = _as_uri(self, resource) ret = c.world_unload_resource(self.world, uri.node) return ret @@ -1359,7 +1370,7 @@ class World(Structure): if isinstance(subject, Port): return subject.get_symbol() - uri = _as_uri(subject) + uri = _as_uri(self, subject) ret = "" if uri is not None: node = c.world_get_symbol(self.world, uri.node) @@ -1372,7 +1383,7 @@ class World(Structure): """Create a new URI node.""" c_node = c.new_uri(self.world, uri) if not c_node: - raise ValueError("Invalid URI '%s'" % uri) + raise ValueError("invalid URI '%s'" % uri) return Node.wrap(self, c_node) diff --git a/bindings/test/python/test_api.py b/bindings/test/python/test_api.py index 652ccd8..62ab6c2 100644 --- a/bindings/test/python/test_api.py +++ b/bindings/test/python/test_api.py @@ -125,6 +125,7 @@ class PluginClassesTests(unittest.TestCase): self.assertIsNotNone(pclass) self.assertTrue(pclass in classes) self.assertTrue(pclass.get_uri() in classes) + self.assertTrue("http://lv2plug.in/ns/lv2core#Plugin" in classes) self.assertGreater(len(classes), 1) self.assertIsNotNone(classes[0]) self.assertIsNotNone(classes[pclass.get_uri()]) @@ -145,7 +146,14 @@ class LoadTests(unittest.TestCase): plugin = plugins.get(plugins.begin()) self.world.load_resource(plugin) self.world.unload_resource(plugin) + self.world.load_resource(plugin.get_uri()) + self.world.unload_resource(plugin.get_uri()) + self.world.load_resource(str(plugin.get_uri())) + self.world.unload_resource(str(plugin.get_uri())) self.world.unload_bundle(self.bundle_uri) + self.world.unload_bundle(str(self.bundle_uri)) + with self.assertRaises(ValueError): + self.world.unload_bundle(4) class PluginTests(unittest.TestCase): @@ -164,7 +172,9 @@ class PluginTests(unittest.TestCase): self.assertTrue(self.plugin.verify()) self.assertTrue(self.plugin in self.plugins) self.assertTrue(self.plugin.get_uri() in self.plugins) + self.assertTrue(str(self.plugin.get_uri()) in self.plugins) self.assertEqual(self.plugins[self.plugin.get_uri()], self.plugin) + self.assertEqual(self.plugins[str(self.plugin.get_uri())], self.plugin) with self.assertRaises(KeyError): self.plugins["http://example.org/notaplugin"].get_uri() @@ -181,6 +191,9 @@ class PluginTests(unittest.TestCase): self.lv2_OutputPort = self.world.new_uri(lilv.LILV_URI_OUTPUT_PORT) self.lv2_AudioPort = self.world.new_uri(lilv.LILV_URI_AUDIO_PORT) self.lv2_ControlPort = self.world.new_uri(lilv.LILV_URI_CONTROL_PORT) + self.params_amplitude = self.world.new_uri( + "http://lv2plug.in/ns/ext/parameters#amplitude" + ) def testGetters(self): self.assertEqual( @@ -197,6 +210,10 @@ class PluginTests(unittest.TestCase): licenses = self.plugin.get_value(self.world.ns.doap.license) features = self.plugin.get_value(self.world.ns.lv2.optionalFeature) self.assertEqual(len(licenses), 1) + self.assertEqual( + self.plugin.get_value("http://usefulinc.com/ns/doap#license")[0], + licenses[0], + ) self.assertTrue(licenses[0] in licenses) with self.assertRaises(IndexError): self.assertIsNone(licenses[len(licenses)]) @@ -211,6 +228,11 @@ class PluginTests(unittest.TestCase): self.assertTrue( self.plugin.has_feature(self.world.ns.lv2.hardRTCapable) ) + self.assertTrue( + self.plugin.has_feature( + "http://lv2plug.in/ns/lv2core#hardRTCapable" + ) + ) self.assertEqual(len(self.plugin.get_supported_features()), 1) self.assertEqual(len(self.plugin.get_optional_features()), 1) self.assertEqual(len(self.plugin.get_required_features()), 0) @@ -219,6 +241,9 @@ class PluginTests(unittest.TestCase): self.world.new_uri("http://example.org/nope") ) ) + self.assertFalse( + self.plugin.has_extension_data("http://example.org/nope") + ) self.assertEqual(len(self.plugin.get_extension_data()), 0) self.assertEqual(len(self.plugin.get_extension_data()), 0) self.assertFalse(self.plugin.has_latency()) @@ -246,6 +271,12 @@ class PluginTests(unittest.TestCase): self.lv2_OutputPort, self.params_amplitude ) ) + self.assertIsNotNone( + self.plugin.get_port_by_designation( + "http://lv2plug.in/ns/lv2core#OutputPort", + "http://lv2plug.in/ns/ext/parameters#amplitude", + ) + ) self.assertIsNone(self.plugin.get_project()) self.assertIsNone(self.plugin.get_author_name()) self.assertIsNone(self.plugin.get_author_email()) @@ -260,6 +291,9 @@ class PluginTests(unittest.TestCase): ), ) self.assertEqual( + 0, len(self.plugin.get_related("http://example.org/Type")), + ) + self.assertEqual( 1, self.plugin.get_num_ports_of_class( self.lv2_InputPort, self.lv2_AudioPort @@ -269,10 +303,20 @@ class PluginTests(unittest.TestCase): self.assertEqual(self.world.get_symbol(port), "input") self.assertTrue(port.get_node().is_blank()) self.assertEqual(0, port.get(self.world.ns.lv2.index)) + self.assertEqual(0, port.get("http://lv2plug.in/ns/lv2core#index")) self.assertEqual(1, len(port.get_value(self.world.ns.lv2.symbol))) + self.assertEqual( + 1, len(port.get_value("http://lv2plug.in/ns/lv2core#symbol")) + ) self.assertEqual(port.get_value(self.world.ns.lv2.symbol)[0], "input") self.assertFalse(port.has_property(self.world.ns.lv2.latency)) + self.assertFalse( + port.has_property("http://lv2plug.in/ns/lv2core#latency") + ) self.assertFalse(port.supports_event(self.world.ns.midi.MidiEvent)) + self.assertFalse( + port.supports_event("http://lv2plug.in/ns/ext/midi#MidiEvent") + ) self.assertEqual(0, port.get_index()) self.assertEqual("input", port.get_symbol()) self.assertEqual("Input", port.get_name()) @@ -284,6 +328,7 @@ class PluginTests(unittest.TestCase): sorted(list(map(str, port.get_classes()))), ) self.assertTrue(port.is_a(self.world.ns.lv2.ControlPort)) + self.assertTrue(port.is_a("http://lv2plug.in/ns/lv2core#ControlPort")) self.assertFalse(port.is_a(self.world.ns.lv2.AudioPort)) self.assertEqual((0.5, 0.0, 1.0), port.get_range()) self.assertEqual(0, len(port.get_properties())) @@ -323,6 +368,13 @@ class PluginTests(unittest.TestCase): self.lv2_InputPort, self.lv2_ControlPort ), ) + self.assertEqual( + 1, + self.plugin.get_num_ports_of_class( + "http://lv2plug.in/ns/lv2core#InputPort", + "http://lv2plug.in/ns/lv2core#ControlPort", + ), + ) class QueryTests(unittest.TestCase): @@ -365,7 +417,7 @@ class InstanceTests(unittest.TestCase): def setUp(self): self.world = lilv.World() self.bundle_uri = self.world.new_uri(location) - self.world.load_bundle(self.bundle_uri) + self.world.load_bundle(str(self.bundle_uri)) self.plugins = self.world.get_all_plugins() self.plugin = self.plugins[0] self.instance = lilv.Instance(self.plugin, 48000) @@ -424,7 +476,11 @@ class UITests(unittest.TestCase): uis[0].get_binary_uri(), str(self.bundle_uri) + "TODO" ) self.assertEqual(uis[uis[0].get_uri()], uis[0]) + self.assertEqual(uis[str(ui_uri)], uis[0]) self.assertTrue(uis[0].is_a(self.world.ns.ui.GtkUI)) + self.assertTrue( + uis[0].is_a("http://lv2plug.in/ns/extensions/ui#GtkUI") + ) self.assertTrue(uis[0] in uis) self.assertTrue(uis[0].get_uri() in uis) self.assertEqual([self.world.ns.ui.GtkUI], list(uis[0].get_classes())) |