Skip to content

Commit 37d0a02

Browse files
committed
docs: clarify Service.interface(), VarlinkError, RequestHandler, and server classes
Improve pydoc documentation to address common points of confusion: - Service.interface() expects a varlink interface name that maps to a .varlink file in interface_dir, not a Python class - VarlinkError subclasses for user-defined errors require manually constructing the {"error": ..., "parameters": {...}} dict - RequestHandler requires a `service` class variable wired to the Service instance - ThreadingServer and ForkingServer live in the varlink package, not socketserver; add docstrings and list all server classes in the module docstring
1 parent c628c54 commit 37d0a02

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-12
lines changed

varlink/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
See https://www.varlink.org for more information about the varlink protocol and interface definition
44
files.
55
6-
For server implementations use the :class:`varlink.Server` class.
6+
For server implementations, create a :class:`varlink.Service`, define your interfaces with the
7+
``@service.interface()`` decorator, wire it to a :class:`varlink.RequestHandler` subclass, and run it
8+
with one of the server classes:
9+
10+
- :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``)
11+
- :class:`varlink.ThreadingServer` -- multi-threaded server for concurrent connections
12+
- :class:`varlink.ForkingServer` -- multi-process server (Unix/Linux only)
713
814
For client implementations use the :class:`varlink.Client` class.
915
10-
For installation and examples, see the GIT repository https://github.com/varlink/python.
16+
For installation and examples, see the GIT repository https://github.com/varlink/python
1117
or the `source code <_modules/varlink/tests/test_orgexamplemore.html>`_ of
1218
:mod:`varlink.tests.test_orgexamplemore`
1319

varlink/error.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,29 @@ def default(self, o):
1616

1717

1818
class VarlinkError(Exception):
19-
"""The base class for varlink error exceptions"""
19+
"""The base class for varlink error exceptions.
20+
21+
User-defined errors
22+
~~~~~~~~~~~~~~~~~~~
23+
24+
To raise custom errors matching ``error`` declarations in a ``.varlink`` file,
25+
subclass ``VarlinkError`` and pass a dict with ``"error"`` and ``"parameters"``
26+
keys to the base ``__init__``. The parameter names and types must match the
27+
``.varlink`` definition manually; there is no automatic binding::
28+
29+
# Given: error ActionFailed (field: string) in the .varlink file
30+
31+
class ActionFailed(VarlinkError):
32+
def __init__(self, reason):
33+
VarlinkError.__init__(self, {
34+
"error": "com.example.ActionFailed",
35+
"parameters": {"field": reason},
36+
})
37+
38+
# In a method handler:
39+
raise ActionFailed("disk full")
40+
41+
"""
2042

2143
@classmethod
2244
def new(cls, message, namespaced=False):

varlink/server.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,29 @@ class Service:
2828
>>> )
2929
3030
For the class implementing the methods of a specific varlink interface
31-
a decorator is used:
31+
the ``@service.interface()`` decorator is used. The argument is a varlink
32+
interface name, **not** a Python class. The decorator loads the file
33+
``{name}.varlink`` from the ``interface_dir`` given to the Service constructor.
34+
For example, ``@service.interface('com.redhat.system.accounts')`` loads
35+
``com.redhat.system.accounts.varlink`` from ``interface_dir``:
3236
3337
>>> @service.interface('com.redhat.system.accounts')
3438
>>> class Accounts:
35-
>>> pass
39+
>>> def GetAccounts(self):
40+
>>> return {"accounts": []}
3641
37-
The varlink file corresponding to this interface is loaded from the 'interface_dir'
38-
specified in the constructor of the Service. It has to end in '.varlink'.
42+
The methods defined on the decorated class directly implement the varlink
43+
interface methods. Each method receives the varlink call parameters as
44+
keyword arguments and must return a dict matching the method's return type.
3945
40-
Use a :class:`RequestHandler` with your Service object and run a :class:`Server` with it.
46+
To wire a Service to a network server, create a :class:`RequestHandler` subclass
47+
and set its ``service`` class variable to your Service instance:
48+
49+
>>> class ServiceRequestHandler(varlink.RequestHandler):
50+
>>> service = service
51+
>>>
52+
>>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler)
53+
>>> server.serve_forever()
4154
4255
If you want to use your own server with the Service object, split the incoming stream
4356
for every null byte and feed it to the :meth:`Service.handle` method.
@@ -275,6 +288,22 @@ def _set_interface(self, filename, interface_class):
275288
return interface_class
276289

277290
def interface(self, filename):
291+
"""Decorator that registers a class as the handler for a varlink interface.
292+
293+
:param filename: A varlink interface name (e.g. ``'com.example.service'``).
294+
The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given
295+
to the Service constructor. This must be an interface name or a file path,
296+
**not** a Python class or type.
297+
298+
Example::
299+
300+
@service.interface('com.example.service')
301+
class Example:
302+
def Echo(self, message):
303+
return {"message": message}
304+
305+
"""
306+
278307
def decorator(interface_class):
279308
self._add_interface(filename, interface_class())
280309
return interface_class
@@ -325,8 +354,17 @@ def get_listen_fd() -> Union[int, None]:
325354
class RequestHandler(StreamRequestHandler):
326355
"""Varlink request handler
327356
328-
To use as an argument for the VarlinkServer constructor.
329-
Instantiate your own class and set the class variable service to your global :class:`Service` object.
357+
Subclass this and set the ``service`` class variable to your :class:`Service` instance.
358+
Then pass your subclass to a :class:`Server` (or :class:`ThreadingServer`) constructor::
359+
360+
class ServiceRequestHandler(varlink.RequestHandler):
361+
service = service # required class variable
362+
363+
server = varlink.ThreadingServer(address, ServiceRequestHandler)
364+
server.serve_forever()
365+
366+
The ``service`` class variable is **required**; without it, incoming requests cannot
367+
be dispatched.
330368
"""
331369

332370
service: Optional[Service] = None
@@ -541,10 +579,12 @@ def __exit__(self, *args):
541579

542580

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

546585

547586
if hasattr(os, "fork"):
548587

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

0 commit comments

Comments
 (0)