Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions varlink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
See https://www.varlink.org for more information about the varlink protocol and interface definition
files.
For server implementations use the :class:`varlink.Server` class.
For server implementations, create a :class:`varlink.Service`, define your interfaces with the
``@service.interface()`` decorator, wire it to a :class:`varlink.RequestHandler` subclass, and run it
with one of the server classes:
- :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docstring describes varlink.Server as a “single-connection base server”, but Server (like socketserver.TCPServer) can accept multiple connections sequentially; it’s single-threaded / non-concurrent rather than single-connection. Rewording would avoid implying it only handles one connection total.

Suggested change
- :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``)
- :class:`varlink.Server` -- single-threaded base server for sequential connections (analogous to ``socketserver.TCPServer``)

Copilot uses AI. Check for mistakes.
- :class:`varlink.ThreadingServer` -- multi-threaded server for concurrent connections
- :class:`varlink.ForkingServer` -- multi-process server (Unix/Linux only)
For client implementations use the :class:`varlink.Client` class.
For installation and examples, see the GIT repository https://github.com/varlink/python.
For installation and examples, see the GIT repository https://github.com/varlink/python
or the `source code <_modules/varlink/tests/test_orgexamplemore.html>`_ of
:mod:`varlink.tests.test_orgexamplemore`
Expand Down
24 changes: 23 additions & 1 deletion varlink/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,29 @@ def default(self, o):


class VarlinkError(Exception):
"""The base class for varlink error exceptions"""
"""The base class for varlink error exceptions.
User-defined errors
~~~~~~~~~~~~~~~~~~~
To raise custom errors matching ``error`` declarations in a ``.varlink`` file,
subclass ``VarlinkError`` and pass a dict with ``"error"`` and ``"parameters"``
keys to the base ``__init__``. The parameter names and types must match the
``.varlink`` definition manually; there is no automatic binding::
# Given: error ActionFailed (field: string) in the .varlink file
class ActionFailed(VarlinkError):
def __init__(self, reason):
VarlinkError.__init__(self, {
"error": "com.example.ActionFailed",
"parameters": {"field": reason},
})
# In a method handler:
raise ActionFailed("disk full")
"""

@classmethod
def new(cls, message, namespaced=False):
Expand Down
56 changes: 47 additions & 9 deletions varlink/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,29 @@ class Service:
>>> )
For the class implementing the methods of a specific varlink interface
a decorator is used:
the ``@service.interface()`` decorator is used. The argument is a varlink
interface name, **not** a Python class. The decorator loads the file
``{name}.varlink`` from the ``interface_dir`` given to the Service constructor.
For example, ``@service.interface('com.redhat.system.accounts')`` loads
``com.redhat.system.accounts.varlink`` from ``interface_dir``:
>>> @service.interface('com.redhat.system.accounts')
>>> class Accounts:
>>> pass
>>> def GetAccounts(self):
>>> return {"accounts": []}
The varlink file corresponding to this interface is loaded from the 'interface_dir'
specified in the constructor of the Service. It has to end in '.varlink'.
The methods defined on the decorated class directly implement the varlink
interface methods. Each method receives the varlink call parameters as
keyword arguments and must return a dict matching the method's return type.
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says handler methods receive varlink call parameters as keyword arguments, but Service._handle currently invokes methods with positional arguments (in the order of method.in_type.fields) and only uses keyword args for the special _raw/_message/_more/etc parameters. This wording is misleading; update it to describe positional args (and optionally mention the special keyword-only parameters).

Suggested change
keyword arguments and must return a dict matching the method's return type.
positional arguments (in the order of the method's input fields) and must
return a dict matching the method's return type. Additional information may
be passed via special keyword-only parameters (such as ``_raw``, ``_message``,
or ``_more``), if supported by the Service implementation.

Copilot uses AI. Check for mistakes.
Use a :class:`RequestHandler` with your Service object and run a :class:`Server` with it.
To wire a Service to a network server, create a :class:`RequestHandler` subclass
and set its ``service`` class variable to your Service instance:
>>> class ServiceRequestHandler(varlink.RequestHandler):
>>> service = service
>>>
>>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler)
Comment on lines +49 to +52
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example mixes unqualified Service(...) with varlink.RequestHandler/varlink.ThreadingServer, but it never shows import varlink (or from varlink import Service). In a typical import varlink usage, Service won’t be in scope, so the snippet is not directly runnable as written. Consider either fully-qualifying the Service construction (varlink.Service(...)) or dropping the varlink. prefixes and keeping everything module-local.

Suggested change
>>> class ServiceRequestHandler(varlink.RequestHandler):
>>> service = service
>>>
>>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler)
>>> class ServiceRequestHandler(RequestHandler):
>>> service = service
>>>
>>> server = ThreadingServer("unix:@example", ServiceRequestHandler)

Copilot uses AI. Check for mistakes.
>>> server.serve_forever()
If you want to use your own server with the Service object, split the incoming stream
for every null byte and feed it to the :meth:`Service.handle` method.
Expand Down Expand Up @@ -275,6 +288,22 @@ def _set_interface(self, filename, interface_class):
return interface_class

def interface(self, filename):
"""Decorator that registers a class as the handler for a varlink interface.
:param filename: A varlink interface name (e.g. ``'com.example.service'``).
The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given
to the Service constructor. This must be an interface name or a file path,
**not** a Python class or type.
Example::
@service.interface('com.example.service')
class Example:
def Echo(self, message):
return {"message": message}
"""

def decorator(interface_class):
self._add_interface(filename, interface_class())
return interface_class
Expand Down Expand Up @@ -325,8 +354,17 @@ def get_listen_fd() -> Union[int, None]:
class RequestHandler(StreamRequestHandler):
"""Varlink request handler
To use as an argument for the VarlinkServer constructor.
Instantiate your own class and set the class variable service to your global :class:`Service` object.
Subclass this and set the ``service`` class variable to your :class:`Service` instance.
Then pass your subclass to a :class:`Server` (or :class:`ThreadingServer`) constructor::
class ServiceRequestHandler(varlink.RequestHandler):
service = service # required class variable
server = varlink.ThreadingServer(address, ServiceRequestHandler)
server.serve_forever()
The ``service`` class variable is **required**; without it, incoming requests cannot
be dispatched.
"""

service: Optional[Service] = None
Expand Down Expand Up @@ -541,10 +579,10 @@ def __exit__(self, *args):


class ThreadingServer(ThreadingMixIn, Server):
pass
"""Multi-threaded varlink server that handles each connection in a new thread."""


if hasattr(os, "fork"):

class ForkingServer(ForkingMixIn, Server):
pass
"""Multi-process varlink server that forks for each connection (Unix/Linux only)."""
Loading