# Usage

## Installation

At present, the project is not on PyPi, but you can install it from git.

    (.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.

    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.

    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.Iterator[int]:
            return iter(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.

    print(DemoInterface.render_interface_description())

It will result in the following interface definition.

    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.

    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.

    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.

    $ 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
    $