Usage ===== Installation ------------ At present, the project is not on PyPi, but you can install it from git. .. code-block:: console (.venv) $ pip install git+https://github.com/helmutg/asyncvarlink Client ------ In order to use the high-level interface, a varlink interface definition needs a corresponding Python class. For the ``org.varlink.service`` introspection interface, there is a ``VarlinkServiceInterface`` Python class available. We can use it to introspect ``systemd-hostnamed`` for example. .. code-block:: python import asyncio from asyncvarlink import VarlinkClientProtocol, connect_unix_varlink from asyncvarlink.serviceinterface import VarlinkServiceInterface async def main() -> None: transport, protocol = await connect_unix_varlink( VarlinkClientProtocol, "/run/systemd/io.systemd.Hostname" ) interfaceproxy = protocol.make_proxy(VarlinkServiceInterface) print(await interfaceproxy.GetInfo()) transport.close() asyncio.run(main()) ``VarlinkServiceInterface`` is the Python implementation of the introspection interface. For non-introspecting use specify your own interface class here. A proxy object needs to be created for each interface that is being used. Defining an interface --------------------- When using ``asyncvarlink`` a varlink interface definition has to be manually translated to a Python class in order to use the interface with type automatic type conversion. Definitions have to inherit from ``asyncvarlink.VarlinkInterface`` and they have to define their interface name (e.g. via a class inheritance argument ``name``). Methods available to varlink have to be decorated with ``asyncvarlink.varlinkmethod`` and they must be precisely typed. Let's go through the following example. .. code-block:: python import asyncio import collections.abc import enum from asyncvarlink import VarlinkInterface, varlinkmethod class Direction(enum.Enum): left = "left" right = "right" class DemoInterface(VarlinkInterface, name="com.example.demo"): @varlinkmethod(return_parameter="direction") def Reverse(self, *, value: Direction) -> Direction: return Direction.left if value == Direction.right else Direction.right @varlinkmethod(return_parameter="value") def Range(self, *, count: int) -> collections.abc.Iterable[int]: return range(count) @varlinkmethod async def Sleep(self, *, delay: float) -> None: await asyncio.sleep(delay) A varlink enum may be represented in Python as an ``enum.Enum`` subclass or be typed as a ``typing.Literal``. In this example, we use the former and define a ``Direction`` type. When looking into methods, note that every call parameter and return parameter must have a name. In case of the ``Reverse`` method, a single call parameter named ``value`` is accepted. On the JSON level, the ``Direction`` type is represented as a string, but ``asyncvarlink`` takes care of converting it from and to the ``Direction`` instances. The ``Reverse`` method returns a single value only and therefore its name may be given via the ``return_parameter`` keyword argument to the decorator. The ``Range`` method demonstrates returning multiple values. A caller of this method must supply the ``more`` keyword. Each integer from the requested range is returned individually as a return parameter named ``value``. The last example demonstrates that method definitions may be asynchronous and delay execution. The special case of returning ``None`` indicates that there are no return parameters and an empty object is returned on the JSON level. We may turn this Python class into a textual varlink interface definition suitable for consumption by other varlink implementations or the introspection interface. .. code-block:: python print(DemoInterface.render_interface_description()) It will result in the following interface definition. .. code-block:: text interface com.example.demo type Direction (left, right) method Range(count: int) -> (value: object) method Reverse(value: Direction) -> (direction: Direction) method Sleep(delay: float) -> () When using ``VarlinkInterface`` subclasses with a client, they are not instantiated and their methods are never called. Therefore, methods may be stubbed using dots (``...``). For use in a server, instances need to be supplied as their methods will be invoked upon client request. Server ------ Let's build a simple server that provides the previously defined demonstration interface as well as the generic introspection interface on stdin/stdout. The relevant protocol class does not consume individual interface instances, but an ``asyncvarlink.VarlinkInterfaceRegistry``. It forms a cycle with the introspection interface as the latter wants to know about all available interfaces, but it also is a registered interface. .. code-block:: python from asyncvarlink import VarlinkInterfaceRegistry from asyncvarlink.serviceinterface import VarlinkServiceInterface registry = VarlinkInterfaceRegistry() registry.register_interface( VarlinkServiceInterface( "ExampleVendor", "DemonstrationProduct", "1.0", "https://github.com/helmutg/asyncvarlink", registry, ), ) registry.register_interface(DemoInterface()) Once the registry is constructed, the communication channel may be established. We attempt to look up an inherited file descriptor named ``varlink`` via systemd's socket activation protocol falling back to communication via stdin and stdout. This is how `varlinkctl`_ communicates with executed servers. .. code-block:: python import sys from asyncvarlink import VarlinkTransport, VarlinkInterfaceServerProtocol, get_listen_fd async def main(): fut = asyncio.get_running_loop().create_future() protocol = VarlinkInterfaceServerProtocol(registry) protocol.connection_lost = fut.set_result listenfd = get_listen_fd("varlink") VarlinkTransport( asyncio.get_running_loop(), listenfd or sys.stdin.fileno(), listenfd or sys.stdout.fileno(), protocol, ) await fut asyncio.run(main()) This allows us to interact with our toy service. .. code-block:: text $ varlinkctl info ./toy_server.py Vendor: ExampleVendor Product: DemonstrationProduct Version: 1.0 URL: https://github.com/helmutg/asyncvarlink Interfaces: com.example.demo org.varlink.service $ .. _varlinkctl: https://www.freedesktop.org/software/systemd/man/latest/varlinkctl.html