1User guide
2==========
3
4.. currentmodule:: pyudev
5
6This guide gives an introduction in how to use pyudev for common operations
7like device enumeration or monitoring:
8
9.. contents::
10
11A detailed reference is provided in the :doc:`API documentation <api/index>`.
12
13
14Getting started
15---------------
16
17Import pyudev and verify that you're using the latest version:
18
19>>> import pyudev
20>>> pyudev.__version__
21u'0.16'
22>>> pyudev.udev_version()
23181
24
25This prints the version of pyudev itself and of the underlying libudev_.
26
27
28A note on versioning
29--------------------
30
31pyudev supports libudev_ 151 or newer, but still tries to cover the most recent
32libudev_ API completely.  If you are using older libudev_ releases, some
33functionality of pyudev may be unavailable, simply because libudev_ is too old
34to support a specific feature.  Whenever this is the case, the minimum required
35version of udev is noted in the documentation (see
36:attr:`Device.is_initialized` for an example).  If no version is specified for
37an attribute or a method, it is available on all supported libudev_ versions.
38You can check the version of the underlying libudev_ with
39:func:`pyudev.udev_version()`.
40
41
42Enumerating devices
43-------------------
44
45A common use case is to enumerate available devices, or a subset thereof.  But
46before you can do anything with pyudev, you need to establish a "connection" to
47the udev device database first.  This connection is represented by a library
48:class:`Context`:
49
50>>> context = pyudev.Context()
51
52The :class:`Context` is the central object of pyudev and libudev_.  You will
53need a :class:`Context` object for almost anything in pyudev.  With the
54``context`` you can now enumerate the available devices:
55
56>>> for device in context.list_devices(): # doctest: +ELLIPSIS
57...     device
58...
59Device(u'/sys/devices/LNXSYSTM:00')
60Device(u'/sys/devices/LNXSYSTM:00/LNXCPU:00')
61Device(u'/sys/devices/LNXSYSTM:00/LNXCPU:01')
62...
63
64By default, :meth:`list_devices()` yields all devices available on the system
65as :class:`Device` objects, but you can filter the list of devices with keyword
66arguments to enumerate all available partitions for example:
67
68>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
69...    print(device)
70...
71Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda1')
72Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda2')
73Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda3')
74
75The choice of the right filters depends on the use case and generally requires
76some knowledge about how udev classifies and categorizes devices.  This is out
77of the scope of this guide.  Poke around in ``/sys/`` to get a feeling for the
78udev-way of device handling, read the udev documentation or one of the
79tutorials in the net.
80
81The keyword arguments of :meth:`list_devices()` provide the most common filter
82operations.  You can apply other, less common filters by calling one of the
83``match_*`` methods on the :class:`Enumerator` returned by of
84:meth:`list_devices()`.
85
86
87Accessing individual devices directly
88-------------------------------------
89
90If you just need a single specific :class:`Device`, you don't need to enumerate
91all devices with a specific filter criterion.  Instead, you can directly create
92:class:`Device` objects from a device path (:meth:`Devices.from_path()`), by
93from a subsystem and device name (:meth:`Devices.from_name()`) or from a device
94file (:meth:`Devices.from_device_file()`).  The following code gets the
95:class:`Device` object for the first hard disc in three different ways:
96
97>>> pyudev.Devices.from_path(context, '/sys/block/sda')
98Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
99>>> pyudev.Devices.from_name(context, 'block', 'sda')
100Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
101>>> pyudev.Devices.from_device_file(context, '/dev/sda')
102Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
103
104As you can see, you need to pass a :class:`Context` to both methods as
105reference to the udev database from which to retrieve information about the
106device.
107
108.. note::
109
110   The :class:`Device` objects created in the above example refer to the same
111   device.  Consequently, they are considered equal:
112
113   >>> pyudev.Devices.from_path(context, '/sys/block/sda') == pyudev.Devices.from_name(context, 'block', 'sda')
114   True
115
116   Whereas :class:`Device` objects referring to different devices are unequal:
117
118   >>> pyudev.Devices.from_name(context, 'block', 'sda') == pyudev.Devices.from_name(context, 'block', 'sda1')
119   False
120
121
122Querying device information
123---------------------------
124
125As you've seen, :class:`Device` represents a device in the udev database.  Each
126such device has a set of "device properties" (not to be confused with Python
127properties as created by :func:`property()`!) that describe the capabilities
128and features of this device as well as its relationship to other devices.
129
130Common device properties are also available as properties of a :class:`Device`
131object.  For instance, you can directly query the :attr:`device_node` and the
132:attr:`device_type` of block devices:
133
134>>> for device in context.list_devices(subsystem='block'):
135...     print('{0} ({1})'.format(device.device_node, device.device_type))
136...
137/dev/sr0 (disk)
138/dev/sda (disk)
139/dev/sda1 (partition)
140/dev/sda2 (partition)
141/dev/sda3 (partition)
142
143For other udev properties, :class:`Device` provides a mapping interface
144to access the device properties by means of its properties attribute.
145
146>>> for device in context.list_devices(subsystem='block'):
147...    print('{0} ({1})'.format(device.properties['DEVNAME'], device.properties['DEVTYPE']))
148...
149/dev/sr0 (disk)
150/dev/sda (disk)
151/dev/sda1 (partition)
152/dev/sda2 (partition)
153/dev/sda3 (partition)
154
155.. warning::
156
157   When filtering devices, you have to use the device property names.  The
158   names of corresponding properties of :class:`Device` will generally **not**
159   work.  Compare the following two statements:
160
161   >>> [device.device_node for device in context.list_devices(subsystem='block', DEVTYPE='partition')]
162   [u'/dev/sda1', u'/dev/sda2', u'/dev/sda3']
163   >>> [device.device_node for device in context.list_devices(subsystem='block', device_type='partition')]
164   []
165
166
167But you can also query many device properties that are not available as Python
168properties on the :class:`Device` object with a convenient mapping interface,
169like the filesystem type.  :class:`Device` provides a convenient mapping
170interface for this purpose:
171
172>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
173...     print('{0} ({1})'.format(device.device_node, device.properties.get('ID_FS_TYPE')))
174...
175/dev/sda1 (ext3)
176/dev/sda2 (swap)
177/dev/sda3 (ext4)
178
179.. note::
180
181   Such device specific properties may not be available on devices.  Either use
182   ``get()`` to specify default values for missing properties, or be prepared
183   to catch :exc:`~exceptions.KeyError`.
184
185Most device properties are computed by udev rules from the driver- and
186device-specific "device attributes".  The :attr:`Device.attributes` mapping
187gives you access to these attributes, but generally you should not need these.
188Use the device properties whenever possible.
189
190
191Examing the device hierarchy
192----------------------------
193
194A :class:`Device` is part of a device hierarchy, and can have a
195:attr:`~Device.parent` device that more or less resembles the physical
196relationship between devices.  For instance, the :attr:`~Device.parent` of
197partition devices is a :class:`Device` object that represents the disc the
198partition is located on:
199
200>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
201...    print('{0} is located on {1}'.format(device.device_node, device.parent.device_node))
202...
203/dev/sda1 is located on /dev/sda
204/dev/sda2 is located on /dev/sda
205/dev/sda3 is located on /dev/sda
206
207Generally, you should not rely on the direct parent-child relationship between
208two devices.  Instead of accessing the parent directly, search for a parent
209within a specific subsystem, e.g. for the parent ``block`` device, with
210:meth:`~Device.find_parent()`:
211
212>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
213...    print('{0} is located on {1}'.format(device.device_node, device.find_parent('block').device_node))
214...
215/dev/sda1 is located on /dev/sda
216/dev/sda2 is located on /dev/sda
217/dev/sda3 is located on /dev/sda
218
219This also save you the tedious work of traversing the device tree manually, if
220you are interested in grand parents, like the name of the PCI slot of the SCSI
221or IDE controller of the disc that contains a partition:
222
223>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
224...    print('{0} attached to PCI slot {1}'.format(device.device_node, device.find_parent('pci')['PCI_SLOT_NAME']))
225...
226/dev/sda1 attached to PCI slot 0000:00:0d.0
227/dev/sda2 attached to PCI slot 0000:00:0d.0
228/dev/sda3 attached to PCI slot 0000:00:0d.0
229
230
231Monitoring devices
232------------------
233
234Synchronous monitoring
235~~~~~~~~~~~~~~~~~~~~~~
236
237The Linux kernel emits events whenever devices are added, removed (e.g. a USB
238stick was plugged or unplugged) or have their attributes changed (e.g. the
239charge level of the battery changed).  With :class:`pyudev.Monitor` you can
240react on such events, for example to react on added or removed mountable
241filesystems:
242
243>>> monitor = pyudev.Monitor.from_netlink(context)
244>>> monitor.filter_by('block')
245>>> for device in iter(monitor.poll, None):
246...     if 'ID_FS_TYPE' in device:
247...         print('{0} partition {1}'.format(device.action, device.get('ID_FS_LABEL')))
248...
249add partition MULTIBOOT
250remove partition MULTIBOOT
251
252After construction of a monitor, you can install an event filter on the monitor
253using :meth:`~Monitor.filter_by()`.  In the above example only events from the
254``block`` subsystem are handled.
255
256.. note::
257
258   Always prefer :meth:`~Monitor.filter_by()` and
259   :meth:`~Monitor.filter_by_tag()` over manually filtering devices (e.g. by
260   ``device.subsystem == 'block'`` or ``tag in device.tags``).  These methods
261   install the filter on the *kernel side*.  A process waiting for events is
262   thus only woken up for events that match these filters.  This is much nicer
263   in terms of power consumption and system load than executing filters in the
264   process itself.
265
266Eventually, you can receive events from the monitor.  As you can see, a
267:class:`Monitor` is iterable and synchronously yields occurred events.  If you
268iterate over a :class:`Monitor`, you will synchronously receive events in an
269endless loop, until you raise an exception, or ``break`` the loop.
270
271This is the quick and dirty way of monitoring, suitable for small scripts or
272quick experiments.  In most cases however, simply iterating over the monitor is
273not sufficient, because it blocks the main thread, and can only be stopped if
274an event occurs (otherwise the loop is not entered and you have no chance to
275``break`` it).
276
277
278Asynchronous monitoring
279~~~~~~~~~~~~~~~~~~~~~~~
280
281For such use cases, pyudev provides asynchronous monitoring with
282:class:`MonitorObserver`.  You can use it to log added and removed mountable
283filesystems to a file, for example:
284
285>>> monitor = pyudev.Monitor.from_netlink(context)
286>>> monitor.filter_by('block')
287>>> def log_event(device):
288...    if 'ID_FS_TYPE' in device.properties:
289...        with open('filesystems.log', 'a+') as stream:
290...            print('{0} - {1}'.format(device.action, device.get('ID_FS_LABEL')), file=stream)
291...
292>>> observer = pyudev.MonitorObserver(monitor, callback=log_event)
293>>> observer.start()
294
295The ``observer`` gets a callback (``log_event()`` in this case) which is
296asynchronously invoked on every event emitted by the underlying ``monitor``
297after the observer has been started using :meth:`~threading.Thread.start()`.
298
299.. warning::
300
301   The callback is invoked from a *different* thread than the one in which the
302   ``observer`` was created.  Be sure to protect access to shared resource
303   properly when you access them from the callback (e.g. by locking).
304
305The ``observer`` can be stopped at any moment using :meth:`~MonitorObserver.stop()``:
306
307>>> observer.stop()
308
309.. warning::
310
311   Do *not* call :meth:`~MonitorObserver.stop()` from the event handler,
312   neither directly nor indirectly.  Use :meth:`~MonitorObserver.send_stop()`
313   if you need to stop monitoring from inside the event handler.
314
315
316GUI toolkit integration
317~~~~~~~~~~~~~~~~~~~~~~~
318
319If you're using a GUI toolkit, you already have the event system of the GUI
320toolkit at hand.  pyudev provides observer classes that seamlessly integration
321in the event system of the GUI toolkit and relieve you from caring with
322synchronisation issues that would occur with thread-based monitoring as
323implemented by :class:`MonitorObserver`.
324
325pyudev supports all major GUI toolkits available for Python:
326
327- Qt_ 5 using :mod:`pyudev.pyqt5`
328- Qt_ 4 using :mod:`pyudev.pyqt4` for the PyQt4_ binding or :mod:`pyudev.pyside`
329  for the PySide_ binding
330- PyGtk_ 2 using :mod:`pyudev.glib`
331- wxWidgets_ and wxPython_ using :mod:`pyudev.wx`
332
333Each of these modules provides an observer class that observers the monitor
334asynchronously and emits proper signals upon device events.
335
336For instance, the above example would look like this in a PySide_ application:
337
338>>> from pyudev.pyside import QUDevMonitorObserver
339>>> monitor = pyudev.Monitor.from_netlink(context)
340>>> observer = QUDevMonitorObserver(monitor)
341>>> observer.deviceEvent.connect(log_event)
342>>> monitor.start()
343
344Device objects as booleans
345~~~~~~~~~~~~~~~~~~~~~~~~~~
346The use of a Device object in a boolean context as a shorthand for a comparison
347with None is an error.
348
349The Device class inherits from the abstract Mapping class, as it maps udev
350property names to their values. Consequently, if a Device object has no udev
351properties, an unusual but not impossible occurance, the object is
352interpreted as False in a boolean context.
353
354.. _pypi: https://pypi.python.org/pypi/pyudev
355.. _libudev: http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
356.. _Qt: http://qt.io/developers/
357.. _PyQt5: https://riverbankcomputing.co.uk/software/pyqt/intro
358.. _PyQt4: https://riverbankcomputing.co.uk/software/pyqt/intro
359.. _PySide: http://wiki.qt.io/PySide
360.. _PyGtk: http://www.pygtk.org/
361.. _wxWidgets: http://wxwidgets.org
362.. _wxPython: http://www.wxpython.org
363