feat(introspect): implement annotations (#359)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
f58f1a6466
commit
5b61869bae
@ -7,7 +7,25 @@ from .signature import SignatureType, get_signature_tree
|
||||
from .validators import assert_interface_name_valid, assert_member_name_valid
|
||||
|
||||
# https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
|
||||
# TODO annotations
|
||||
|
||||
|
||||
def _fetch_annotations(element: ET.Element) -> dict[str, str]:
|
||||
annotations: dict[str, str] = {}
|
||||
|
||||
for child in element:
|
||||
if child.tag != "annotation":
|
||||
continue
|
||||
annotation_name = child.attrib["name"]
|
||||
annotation_value = child.attrib["value"]
|
||||
annotations[annotation_name] = annotation_value
|
||||
|
||||
return annotations
|
||||
|
||||
|
||||
def _extract_annotations(element: ET.Element, annotations: dict[str, str]) -> None:
|
||||
for key, value in annotations.items():
|
||||
annotation = ET.Element("annotation", {"name": key, "value": value})
|
||||
element.append(annotation)
|
||||
|
||||
|
||||
class Arg:
|
||||
@ -21,6 +39,8 @@ class Arg:
|
||||
:vartype type: :class:`SignatureType <dbus_fast.SignatureType>`
|
||||
:ivar signature: The signature string of this argument.
|
||||
:vartype signature: str
|
||||
:ivar annotations: The annotations of this arg.
|
||||
:vartype annotations: dict[str, str]
|
||||
|
||||
:raises:
|
||||
- :class:`InvalidMemberNameError <dbus_fast.InvalidMemberNameError>` - If the name of the arg is not valid.
|
||||
@ -33,6 +53,7 @@ class Arg:
|
||||
signature: Union[SignatureType, str],
|
||||
direction: Optional[list[ArgDirection]] = None,
|
||||
name: Optional[str] = None,
|
||||
annotations: Optional[dict[str, str]] = None,
|
||||
):
|
||||
type_ = None
|
||||
if type(signature) is SignatureType:
|
||||
@ -50,6 +71,7 @@ class Arg:
|
||||
self.signature = signature
|
||||
self.name = name
|
||||
self.direction = direction
|
||||
self.annotations = annotations or {}
|
||||
|
||||
def from_xml(element: ET.Element, direction: ArgDirection) -> "Arg":
|
||||
"""Convert a :class:`xml.etree.ElementTree.Element` into a
|
||||
@ -73,7 +95,9 @@ class Arg:
|
||||
'a method argument must have a "type" attribute'
|
||||
)
|
||||
|
||||
return Arg(signature, direction, name)
|
||||
annotations = _fetch_annotations(element)
|
||||
|
||||
return Arg(signature, direction, name, annotations)
|
||||
|
||||
def to_xml(self) -> ET.Element:
|
||||
"""Convert this :class:`Arg` into an :class:`xml.etree.ElementTree.Element`."""
|
||||
@ -85,6 +109,8 @@ class Arg:
|
||||
element.set("direction", self.direction.value)
|
||||
element.set("type", self.signature)
|
||||
|
||||
_extract_annotations(element, self.annotations)
|
||||
|
||||
return element
|
||||
|
||||
|
||||
@ -97,18 +123,26 @@ class Signal:
|
||||
:vartype args: list(Arg)
|
||||
:ivar signature: The collected signature of the output arguments.
|
||||
:vartype signature: str
|
||||
:ivar annotations: The annotations of this signal.
|
||||
:vartype annotations: dict[str, str]
|
||||
|
||||
:raises:
|
||||
- :class:`InvalidMemberNameError <dbus_fast.InvalidMemberNameError>` - If the name of the signal is not a valid member name.
|
||||
"""
|
||||
|
||||
def __init__(self, name: Optional[str], args: Optional[list[Arg]] = None):
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str],
|
||||
args: Optional[list[Arg]] = None,
|
||||
annotations: Optional[dict[str, str]] = None,
|
||||
):
|
||||
if name is not None:
|
||||
assert_member_name_valid(name)
|
||||
|
||||
self.name = name
|
||||
self.args = args or []
|
||||
self.signature = "".join(arg.signature for arg in self.args)
|
||||
self.annotations = annotations or {}
|
||||
|
||||
def from_xml(element):
|
||||
"""Convert an :class:`xml.etree.ElementTree.Element` to a :class:`Signal`.
|
||||
@ -132,7 +166,9 @@ class Signal:
|
||||
if child.tag == "arg":
|
||||
args.append(Arg.from_xml(child, ArgDirection.OUT))
|
||||
|
||||
signal = Signal(name, args)
|
||||
annotations = _fetch_annotations(element)
|
||||
|
||||
signal = Signal(name, args, annotations)
|
||||
|
||||
return signal
|
||||
|
||||
@ -144,6 +180,8 @@ class Signal:
|
||||
for arg in self.args:
|
||||
element.append(arg.to_xml())
|
||||
|
||||
_extract_annotations(element, self.annotations)
|
||||
|
||||
return element
|
||||
|
||||
|
||||
@ -160,12 +198,20 @@ class Method:
|
||||
:vartype in_signature: str
|
||||
:ivar out_signature: The collected signature string of the output arguments.
|
||||
:vartype out_signature: str
|
||||
:ivar annotations: The annotations of this method.
|
||||
:vartype annotations: dict[str, str]
|
||||
|
||||
:raises:
|
||||
- :class:`InvalidMemberNameError <dbus_fast.InvalidMemberNameError>` - If the name of this method is not valid.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, in_args: list[Arg] = [], out_args: list[Arg] = []):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
in_args: list[Arg] = [],
|
||||
out_args: list[Arg] = [],
|
||||
annotations: Optional[dict[str, str]] = None,
|
||||
):
|
||||
assert_member_name_valid(name)
|
||||
|
||||
self.name = name
|
||||
@ -173,6 +219,7 @@ class Method:
|
||||
self.out_args = out_args
|
||||
self.in_signature = "".join(arg.signature for arg in in_args)
|
||||
self.out_signature = "".join(arg.signature for arg in out_args)
|
||||
self.annotations = annotations or {}
|
||||
|
||||
def from_xml(element: ET.Element) -> "Method":
|
||||
"""Convert an :class:`xml.etree.ElementTree.Element` to a :class:`Method`.
|
||||
@ -203,7 +250,9 @@ class Method:
|
||||
elif direction == ArgDirection.OUT:
|
||||
out_args.append(arg)
|
||||
|
||||
return Method(name, in_args, out_args)
|
||||
annotations = _fetch_annotations(element)
|
||||
|
||||
return Method(name, in_args, out_args, annotations)
|
||||
|
||||
def to_xml(self) -> ET.Element:
|
||||
"""Convert this :class:`Method` into an :class:`xml.etree.ElementTree.Element`."""
|
||||
@ -215,6 +264,8 @@ class Method:
|
||||
for arg in self.out_args:
|
||||
element.append(arg.to_xml())
|
||||
|
||||
_extract_annotations(element, self.annotations)
|
||||
|
||||
return element
|
||||
|
||||
|
||||
@ -230,6 +281,8 @@ class Property:
|
||||
:vartype access: :class:`PropertyAccess <dbus_fast.PropertyAccess>`
|
||||
:ivar type: The parsed type of this property.
|
||||
:vartype type: :class:`SignatureType <dbus_fast.SignatureType>`
|
||||
:ivar annotations: The annotations of this property.
|
||||
:vartype annotations: dict[str, str]
|
||||
|
||||
:raises:
|
||||
- :class:`InvalidIntrospectionError <dbus_fast.InvalidIntrospectionError>` - If the property is not a single complete type.
|
||||
@ -242,6 +295,7 @@ class Property:
|
||||
name: str,
|
||||
signature: str,
|
||||
access: PropertyAccess = PropertyAccess.READWRITE,
|
||||
annotations: Optional[dict[str, str]] = None,
|
||||
validate: bool = True,
|
||||
):
|
||||
if validate:
|
||||
@ -257,6 +311,7 @@ class Property:
|
||||
self.signature = signature
|
||||
self.access = access
|
||||
self.type = tree.types[0]
|
||||
self.annotations = annotations or {}
|
||||
|
||||
def from_xml(element, validate: bool = True):
|
||||
"""Convert an :class:`xml.etree.ElementTree.Element` to a :class:`Property`.
|
||||
@ -278,7 +333,11 @@ class Property:
|
||||
if not signature:
|
||||
raise InvalidIntrospectionError('properties must have a "type" attribute')
|
||||
|
||||
return Property(name, signature, access, validate=validate)
|
||||
annotations = _fetch_annotations(element)
|
||||
|
||||
return Property(
|
||||
name, signature, access, annotations=annotations, validate=validate
|
||||
)
|
||||
|
||||
def to_xml(self) -> ET.Element:
|
||||
"""Convert this :class:`Property` into an :class:`xml.etree.ElementTree.Element`."""
|
||||
@ -286,6 +345,7 @@ class Property:
|
||||
element.set("name", self.name)
|
||||
element.set("type", self.signature)
|
||||
element.set("access", self.access.value)
|
||||
_extract_annotations(element, self.annotations)
|
||||
return element
|
||||
|
||||
|
||||
@ -303,6 +363,8 @@ class Interface:
|
||||
:vartype signals: list(:class:`Signal`)
|
||||
:ivar properties: A list of properties exposed on this interface.
|
||||
:vartype properties: list(:class:`Property`)
|
||||
:ivar annotations: The annotations of this interface.
|
||||
:vartype annotations: dict[str, str]
|
||||
|
||||
:raises:
|
||||
- :class:`InvalidInterfaceNameError <dbus_fast.InvalidInterfaceNameError>` - If the name is not a valid interface name.
|
||||
@ -314,6 +376,7 @@ class Interface:
|
||||
methods: Optional[list[Method]] = None,
|
||||
signals: Optional[list[Signal]] = None,
|
||||
properties: Optional[list[Property]] = None,
|
||||
annotations: Optional[dict[str, str]] = None,
|
||||
):
|
||||
assert_interface_name_valid(name)
|
||||
|
||||
@ -321,6 +384,7 @@ class Interface:
|
||||
self.methods = methods if methods is not None else []
|
||||
self.signals = signals if signals is not None else []
|
||||
self.properties = properties if properties is not None else []
|
||||
self.annotations = annotations or {}
|
||||
|
||||
@staticmethod
|
||||
def from_xml(
|
||||
@ -353,6 +417,8 @@ class Interface:
|
||||
Property.from_xml(child, validate=validate_property_names)
|
||||
)
|
||||
|
||||
interface.annotations = _fetch_annotations(element)
|
||||
|
||||
return interface
|
||||
|
||||
def to_xml(self) -> ET.Element:
|
||||
@ -367,6 +433,8 @@ class Interface:
|
||||
for prop in self.properties:
|
||||
element.append(prop.to_xml())
|
||||
|
||||
_extract_annotations(element, self.annotations)
|
||||
|
||||
return element
|
||||
|
||||
|
||||
|
||||
@ -97,8 +97,7 @@ def test_example_introspection_to_xml():
|
||||
method = interface[0]
|
||||
assert method.tag == "method"
|
||||
assert method.get("name") == "Frobate"
|
||||
# TODO annotations
|
||||
assert len(method) == 3
|
||||
assert len(method) == 4
|
||||
|
||||
arg = method[0]
|
||||
assert arg.tag == "arg"
|
||||
@ -106,6 +105,11 @@ def test_example_introspection_to_xml():
|
||||
assert arg.attrib.get("type") == "i"
|
||||
assert arg.attrib.get("direction") == "in"
|
||||
|
||||
annotation = method[3]
|
||||
assert annotation.tag == "annotation"
|
||||
assert annotation.attrib.get("name") == "org.freedesktop.DBus.Deprecated"
|
||||
assert annotation.attrib.get("value") == "true"
|
||||
|
||||
signal = interface[3]
|
||||
assert signal.tag == "signal"
|
||||
assert signal.attrib.get("name") == "Changed"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user