pyudev-0.21.0/ 0000775 0001750 0001750 00000000000 12744231232 014256 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/README.rst 0000664 0001750 0001750 00000006652 12710747516 015770 0 ustar mulhern mulhern 0000000 0000000 ######
pyudev
######
.. image:: https://secure.travis-ci.org/pyudev/pyudev.png?branch=develop
:target: http://travis-ci.org/pyudev/pyudev
http://pyudev.readthedocs.org
pyudev is a LGPL_ licensed, pure Python_ binding for libudev_, the device and
hardware management and information library for Linux. It supports almost all
libudev_ functionality. You can enumerate devices, query device properties and
attributes or monitor devices, including asynchronous monitoring with threads,
or within the event loops of Qt, Glib or wxPython.
The binding supports CPython_ 2 (2.6 or newer) and 3 (3.1 or newer), and PyPy_
1.5 or newer. It is tested against udev 151 or newer, earlier versions of udev
as found on dated Linux systems may work, but are not officially supported.
Usage
-----
Usage of pyudev is quite simply thanks to the power of the underlying udev
library. Getting the labels of all partitions just takes a few lines:
>>> import pyudev
>>> context = pyudev.Context()
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print(device.get('ID_FS_LABEL', 'unlabeled partition'))
...
boot
swap
system
The website_ provides a detailed `user guide`_ and a complete `API reference`_.
Support
-------
Please report issues and questions to the issue tracker, but respect the
following guidelines:
- Check that the issue has not already been reported.
- Check that the issue is not already fixed in the ``master`` branch.
- Open issues with clear title and a detailed description in grammatically
correct, complete sentences.
- Include the Python version and the udev version (see ``udevadm --version``) in
the description of your issue.
Development
-----------
The source code is hosted on GitHub_::
git clone git://github.com/pyudev/pyudev.git
Please fork the repository and send pull requests with your fixes or new
features, but respect the following guidelines:
- Read `how to properly contribute to open source projects on GitHub
`_.
- Understand the `branching model
`_.
- Use a topic branch based on the ``develop`` branch to easily amend a pull
request later, if necessary.
- Write `good commit messages
`_.
- Squash commits on the topic branch before opening a pull request.
- Respect :pep:`8` (use pep8_ to check your coding style compliance).
- Add unit tests if possible (refer to the `testsuite documentation
`_).
- Add API documentation in docstrings.
- Open a `pull request `_
that relates to but one subject with a clear title and description in
grammatically correct, complete sentences.
.. _LGPL: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
.. _Python: http://www.python.org/
.. _CPython: http://www.python.org/
.. _PyPy: http://www.pypy.org/
.. _libudev: http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
.. _website: http://pyudev.readthedocs.org
.. _user guide: http://pyudev.readthedocs.org/en/latest/guide.html
.. _api reference: http://pyudev.readthedocs.org/en/latest/api/index.html
.. _issue tracker: http://github.com/lunaryorn/pyudev/issues
.. _GitHub: http://github.com/lunaryorn/pyudev
.. _git: http://www.git-scm.com/
.. _pep8: http://pypi.python.org/pypi/pep8/
pyudev-0.21.0/COPYING 0000664 0001750 0001750 00000063500 12654153421 015320 0 ustar mulhern mulhern 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
Copyright (C)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
pyudev-0.21.0/tests/ 0000775 0001750 0001750 00000000000 12744231232 015420 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/tests/test_monitor.py 0000664 0001750 0001750 00000035035 12744230565 020536 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import random
from datetime import datetime, timedelta
from contextlib import contextmanager
from select import select
import pytest
import mock
from pyudev import Monitor, MonitorObserver, Devices
from tests.utils.udev import DeviceDatabase
from tests._constants import _UDEV_TEST
# many tests just consist of some monkey patching to test, that the Monitor
# class actually calls out to udev, correctly passing arguments and handling
# return value. Actual udev calls are difficult to test, as return values
# and side effects are dynamic and environment-dependent. It isn't
# necessary anyway, libudev can just assumed to be correct.
@pytest.fixture
def monitor(request):
return Monitor.from_netlink(request.getfuncargvalue('context'))
@pytest.fixture
def fake_monitor_device(request):
context = request.getfuncargvalue('context')
device = random.choice(list(DeviceDatabase.db()))
return Devices.from_path(context, device.device_path)
@contextmanager
def patch_filter_by(type):
add_match = 'udev_monitor_filter_add_match_{0}'.format(type)
filter_update = 'udev_monitor_filter_update'
with pytest.patch_libudev(add_match) as add_match:
add_match.return_value = 0
with pytest.patch_libudev(filter_update) as filter_update:
filter_update.return_value = 0
yield add_match, filter_update
class TestMonitor(object):
def test_from_netlink_invalid_source(self, context):
with pytest.raises(ValueError) as exc_info:
Monitor.from_netlink(context, source='invalid_source')
message = ('Invalid source: {0!r}. Must be one of "udev" '
'or "kernel"'.format('invalid_source'))
assert str(exc_info.value) == message
def test_from_netlink_source_udev(self, context):
monitor = Monitor.from_netlink(context)
assert monitor._as_parameter_
assert not monitor.started
monitor = Monitor.from_netlink(context, source='udev')
assert monitor._as_parameter_
assert not monitor.started
def test_from_netlink_source_udev_mock(self, context):
funcname = 'udev_monitor_new_from_netlink'
spec = lambda c, s: None
with mock.patch.object(context._libudev, funcname,
autospec=spec) as func:
func.return_value = mock.sentinel.monitor
monitor = Monitor.from_netlink(context)
assert monitor._as_parameter_ is mock.sentinel.monitor
assert not monitor.started
func.assert_called_once_with(context, b'udev')
func.reset_mock()
monitor = Monitor.from_netlink(context, 'udev')
assert monitor._as_parameter_ is mock.sentinel.monitor
assert not monitor.started
func.assert_called_once_with(context, b'udev')
def test_from_netlink_source_kernel(self, context):
monitor = Monitor.from_netlink(context, source='kernel')
assert monitor._as_parameter_
assert not monitor.started
def test_from_netlink_source_kernel_mock(self, context):
funcname = 'udev_monitor_new_from_netlink'
spec = lambda c, s: None
with mock.patch.object(context._libudev, funcname,
autospec=spec) as func:
func.return_value = mock.sentinel.monitor
monitor = Monitor.from_netlink(context, 'kernel')
assert monitor._as_parameter_ is mock.sentinel.monitor
assert not monitor.started
func.assert_called_once_with(context, b'kernel')
def test_fileno(self, monitor):
# we can't do more than check that no exception is thrown
monitor.fileno()
def test_fileno_mock(self, monitor):
funcname = 'udev_monitor_get_fd'
spec = lambda m: None
with mock.patch.object(monitor._libudev, funcname,
autospec=spec) as func:
func.return_value = mock.sentinel.fileno
assert monitor.fileno() is mock.sentinel.fileno
func.assert_called_once_with(monitor)
def test_filter_by_no_subsystem(self, monitor):
with pytest.raises(AttributeError):
monitor.filter_by(None)
def test_filter_by_subsystem_no_dev_type(self, monitor):
monitor.filter_by(b'input')
monitor.filter_by('input')
def test_filter_by_subsystem_no_dev_type_mock(self, monitor):
funcname = 'udev_monitor_filter_add_match_subsystem_devtype'
spec = lambda m, s, t: None
libudev = monitor._libudev
with mock.patch.object(libudev, funcname, autospec=spec) as match:
funcname = 'udev_monitor_filter_update'
spec = lambda m: None
with mock.patch.object(libudev, funcname, autospec=spec) as update:
monitor.filter_by('input')
match.assert_called_once_with(monitor, b'input', None)
update.assert_called_once_with(monitor)
def test_filter_by_subsystem_dev_type(self, monitor):
monitor.filter_by('input', b'usb_interface')
monitor.filter_by('input', 'usb_interface')
def test_filter_by_subsystem_dev_type_mock(self, monitor):
funcname = 'udev_monitor_filter_add_match_subsystem_devtype'
spec = lambda m, s, t: None
libudev = monitor._libudev
with mock.patch.object(libudev, funcname, autospec=spec) as match:
funcname = 'udev_monitor_filter_update'
spec = lambda m: None
with mock.patch.object(libudev, funcname, autospec=spec) as update:
monitor.filter_by('input', 'usb_interface')
match.assert_called_once_with(monitor, b'input',
b'usb_interface')
update.assert_called_once_with(monitor)
@_UDEV_TEST(154, "test_filter_by_tag")
def test_filter_by_tag(self, monitor):
monitor.filter_by_tag('spam')
@_UDEV_TEST(154, "test_filter_by_tag")
def test_filter_by_tag_mock(self, monitor):
funcname = 'udev_monitor_filter_add_match_tag'
spec = lambda m, t: None
libudev = monitor._libudev
with mock.patch.object(libudev, funcname, autospec=spec) as match:
funcname = 'udev_monitor_filter_update'
spec = lambda m: None
with mock.patch.object(libudev, funcname, autospec=spec) as update:
monitor.filter_by_tag('eggs')
match.assert_called_once_with(monitor, b'eggs')
update.assert_called_once_with(monitor)
def test_remove_filter(self, monitor):
"""
The underlying ``udev_monitor_filter_remove()`` is apparently broken.
It always causes ``EINVAL`` from ``setsockopt()``.
"""
with pytest.raises(ValueError):
monitor.remove_filter()
def test_remove_filter_mock(self, monitor):
funcname = 'udev_monitor_filter_remove'
libudev = monitor._libudev
spec = lambda m: None
with mock.patch.object(libudev, funcname, autospec=spec) as remove:
funcname = 'udev_monitor_filter_update'
with mock.patch.object(libudev, funcname, autospec=spec) as update:
monitor.remove_filter()
remove.assert_called_once_with(monitor)
update.assert_called_once_with(monitor)
def test_start_netlink_kernel_source(self, context):
monitor = Monitor.from_netlink(context, source='kernel')
assert not monitor.started
monitor.start()
assert monitor.started
def test_start_mock(self, monitor):
funcname = 'udev_monitor_enable_receiving'
spec = lambda m: None
with mock.patch.object(monitor._libudev, funcname,
autospec=spec) as func:
assert not monitor.started
monitor.start()
assert monitor.started
monitor.start()
func.assert_called_once_with(monitor)
def test_enable_receiving(self, monitor):
"""
Test that enable_receiving() is deprecated and calls out to start().
"""
with mock.patch.object(monitor, 'start') as start:
pytest.deprecated_call(monitor.enable_receiving)
assert start.called
def test_set_receive_buffer_size_mock(self, monitor):
funcname = 'udev_monitor_set_receive_buffer_size'
spec = lambda m, s: None
with mock.patch.object(monitor._libudev, funcname,
autospec=spec) as func:
monitor.set_receive_buffer_size(1000)
func.assert_called_once_with(monitor, 1000)
def test_poll_timeout(self, monitor):
assert monitor.poll(timeout=0) is None
now = datetime.now()
assert monitor.poll(timeout=1) is None
assert datetime.now() - now >= timedelta(seconds=1)
@pytest.mark.privileged
@pytest.mark.not_on_travis
def test_poll(self, monitor):
# forcibly unload the dummy module to avoid hangs
pytest.unload_dummy()
monitor.filter_by('net')
monitor.start()
# load the dummy device to trigger an add event
pytest.load_dummy()
select([monitor], [], [])
device = monitor.poll()
assert device.action == 'add'
assert device.sequence_number > 0
assert device.subsystem == 'net'
assert device.device_path == '/devices/virtual/net/dummy0'
# and unload again
pytest.unload_dummy()
device = monitor.poll()
assert device.action == 'remove'
assert device.sequence_number > 0
assert device.subsystem == 'net'
assert device.device_path == '/devices/virtual/net/dummy0'
def test_receive_device(self, monitor):
"""
Test that Monitor.receive_device is deprecated and calls out to
poll(), which in turn is tested by test_poll.
"""
with mock.patch.object(monitor, 'poll') as poll:
device = mock.Mock(name='device')
device.action = 'spam'
poll.return_value = device
event = pytest.deprecated_call(monitor.receive_device)
assert event[0] == 'spam'
assert event[1] is device
@pytest.mark.privileged
@pytest.mark.not_on_travis
def test_iter(self, monitor):
pytest.unload_dummy()
monitor.filter_by('net')
monitor.start()
pytest.load_dummy()
iterator = iter(monitor)
# DeprecationWarning triggered on first invocation of generator
action, device = pytest.deprecated_call(next, iterator)
assert action == 'add'
assert device.action == 'add'
assert device.sequence_number > 0
assert device.subsystem == 'net'
assert device.device_path == '/devices/virtual/net/dummy0'
pytest.unload_dummy()
action, device = next(iterator)
assert action == 'remove'
assert device.action == 'remove'
assert device.sequence_number > 0
assert device.subsystem == 'net'
assert device.device_path == '/devices/virtual/net/dummy0'
iterator.close()
class TestMonitorObserver(object):
def callback(self, device):
self.events.append(device)
if len(self.events) >= 2:
self.observer.send_stop()
def event_handler(self, action, device):
self.events.append((action, device))
if len(self.events) >= 2:
self.observer.send_stop()
def make_observer(self, monitor, use_deprecated=False):
if use_deprecated:
if pytest.__version__ == '2.8.4':
self.observer = MonitorObserver(
monitor,
event_handler=self.event_handler
)
else:
self.observer = pytest.deprecated_call(
MonitorObserver,
monitor,
event_handler=self.event_handler
)
else:
self.observer = MonitorObserver(monitor, callback=self.callback)
return self.observer
def setup(self):
self.events = []
def teardown(self):
self.events = None
def test_deprecated_handler(self, fake_monitor, fake_monitor_device):
observer = self.make_observer(fake_monitor, use_deprecated=True)
observer.start()
fake_monitor.trigger_event()
fake_monitor.trigger_event()
# wait a second for the tests to finish, and kill the observer if
# it is still alive then
observer.join(1)
if observer.is_alive():
observer.stop()
assert not observer.is_alive()
assert self.events == [(None, fake_monitor_device)] * 2
def test_fake(self, fake_monitor, fake_monitor_device):
observer = self.make_observer(fake_monitor)
observer.start()
fake_monitor.trigger_event()
fake_monitor.trigger_event()
# wait a second for the tests to finish
observer.join(1)
# forcibly quit the thread if it is still alive
if observer.is_alive():
observer.stop()
assert not observer.is_alive()
# check that we got two events
assert self.events == [fake_monitor_device] * 2
@pytest.mark.privileged
@pytest.mark.not_on_travis
def test_real(self, context, monitor):
observer = self.make_observer(monitor)
pytest.unload_dummy()
monitor.filter_by('net')
monitor.start()
observer.start()
pytest.load_dummy()
pytest.unload_dummy()
observer.join(2)
if observer.is_alive():
observer.stop()
assert not observer.is_alive()
assert [d.action for d in self.events] == ['add', 'remove']
for device in self.events:
assert device.device_path == '/devices/virtual/net/dummy0'
pyudev-0.21.0/tests/test_util.py 0000664 0001750 0001750 00000014454 12710747516 020030 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import sys
import pytest
from mock import Mock
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
from pyudev import _util
from pyudev import Context
from .utils import is_unicode_string
_CONTEXT = Context()
@pytest.mark.conversion
def test_ensure_byte_string():
assert isinstance(_util.ensure_byte_string('hello world'), bytes)
assert _util.ensure_byte_string('hello world') == b'hello world'
hello = b'hello world'
assert _util.ensure_byte_string(hello) is hello
@pytest.mark.conversion
def test_ensure_byte_string_none():
with pytest.raises(AttributeError):
_util.ensure_byte_string(None)
@pytest.mark.conversion
def test_ensure_unicode_string():
assert is_unicode_string(
_util.ensure_unicode_string(b'hello world'))
assert _util.ensure_unicode_string(b'hello world') == 'hello world'
hello = 'hello world'
assert _util.ensure_unicode_string(hello) is hello
@pytest.mark.conversion
def test_ensure_unicode_string_none():
with pytest.raises(AttributeError):
_util.ensure_unicode_string(None)
@pytest.mark.conversion
def test_property_value_to_bytes_string():
hello = 'hello world'.encode(sys.getfilesystemencoding())
assert _util.property_value_to_bytes(hello) is hello
assert isinstance(_util.property_value_to_bytes('hello world'), bytes)
assert _util.property_value_to_bytes('hello world') == hello
@pytest.mark.conversion
def test_property_value_to_bytes_int():
assert _util.property_value_to_bytes(10000) == b'10000'
assert isinstance(_util.property_value_to_bytes(10000), bytes)
@pytest.mark.conversion
def test_property_value_to_bytes_bool():
assert _util.property_value_to_bytes(True) == b'1'
assert isinstance(_util.property_value_to_bytes(True), bytes)
assert _util.property_value_to_bytes(False) == b'0'
assert isinstance(_util.property_value_to_bytes(False), bytes)
@pytest.mark.conversion
def test_string_to_bool_true():
assert isinstance(_util.string_to_bool('1'), bool)
assert _util.string_to_bool('1')
@pytest.mark.conversion
def test_string_to_bool_false():
assert isinstance(_util.string_to_bool('0'), bool)
assert not _util.string_to_bool('0')
@pytest.mark.conversion
def test_string_to_bool_invalid_value():
with pytest.raises(ValueError) as exc_info:
_util.string_to_bool('foo')
assert str(exc_info.value) == 'Not a boolean value: {0!r}'.format('foo')
def test_udev_list_iterate_no_entry():
assert not list(_util.udev_list_iterate(Mock(), None))
def test_udev_list_iterate_mock():
libudev = Mock(name='libudev')
items = [('spam', 'eggs'), ('foo', 'bar')]
with pytest.libudev_list(libudev, 'udev_enumerate_get_list_entry', items):
udev_list = libudev.udev_enumerate_get_list_entry()
assert list(_util.udev_list_iterate(libudev, udev_list)) == [
('spam', 'eggs'), ('foo', 'bar')]
def raise_valueerror():
raise ValueError('from function')
_CHAR_DEVICES = list(_CONTEXT.list_devices(subsystem="tty"))
@pytest.mark.skipif(len(_CHAR_DEVICES) == 0, reason='no tty devices')
@given(strategies.sampled_from(_CHAR_DEVICES))
@settings(min_satisfying_examples=1, max_examples=5)
def test_get_device_type_character_device(a_device):
"""
Check that the device type of a character device is actually char.
"""
assert _util.get_device_type(a_device.device_node) == 'char'
_BLOCK_DEVICES = list(_CONTEXT.list_devices(subsystem="block"))
@pytest.mark.skipif(len(_BLOCK_DEVICES) == 0, reason='no block devices')
@given(strategies.sampled_from(_BLOCK_DEVICES))
@settings(min_satisfying_examples=1, max_examples=5)
def test_get_device_type_block_device(a_device):
"""
Check that the device type of a block device is actually block.
"""
assert _util.get_device_type(a_device.device_node) == 'block'
def test_get_device_type_no_device_file(tmpdir):
filename = tmpdir.join('test')
filename.ensure(file=True)
with pytest.raises(ValueError) as excinfo:
_util.get_device_type(str(filename))
message = 'not a device file: {0!r}'.format(str(filename))
assert str(excinfo.value) == message
def test_get_device_type_not_existing(tmpdir):
"""
Test that an OSError is raised when checking device type using a file
that does not actually exist.
"""
filename = tmpdir.join('test_get_device_type_not_existing')
assert not tmpdir.check(file=True)
with pytest.raises(OSError):
_util.get_device_type(str(filename))
def test_eintr_retry_call(tmpdir):
import os, signal, select
def handle_alarm(signum, frame):
# pylint: disable=unused-argument
pass
orig_alarm = signal.getsignal(signal.SIGALRM)
# Open an empty file and use it to wait for exceptions which should
# never happen
filename = tmpdir.join('test')
filename.ensure(file=True)
fd = os.open(str(filename), os.O_RDONLY)
try:
signal.signal(signal.SIGALRM, handle_alarm)
# Ensure that a signal raises EINTR on Python < 3.5
if sys.version_info < (3, 5):
with pytest.raises(select.error):
signal.alarm(1)
select.select([], [], [fd], 2)
# Ensure that wrapping the call does not raise EINTR
signal.alarm(1)
assert _util.eintr_retry_call(select.select, [], [], [3], 2) == ([], [], [])
finally:
os.close(fd)
signal.signal(signal.SIGALRM, orig_alarm)
pyudev-0.21.0/tests/test_pypi.py 0000664 0001750 0001750 00000013326 12744230565 020027 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import sys
import re
import os
import subprocess
from distutils.filelist import FileList
if sys.version_info[0] < 3:
from codecs import open
from urlparse import urlparse
import py.path
import pytest
import docutils.utils # Work around Docutils bug 214
from docutils import io, readers
from docutils.core import publish_doctree, Publisher
from docutils.transforms import TransformError
TEST_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
SOURCE_DIRECTORY = os.path.abspath(os.path.join(
TEST_DIRECTORY, os.pardir))
MANIFEST = os.path.join(SOURCE_DIRECTORY, 'MANIFEST.in')
README = os.path.join(SOURCE_DIRECTORY, 'README.rst')
# Files in the repository that don't need to be present in the sdist
REQUIRED_BLACKLIST = [
r'^\.git.+',
r'\.travis\.yml$',
r'^MANIFEST\.in$',
r'^Makefile$'
]
def _get_required_files():
if not os.path.isdir(os.path.join(SOURCE_DIRECTORY, '.git')):
pytest.skip('Not in git clone')
git = py.path.local.sysfind('git')
if not git:
pytest.skip('git not available')
ls_files = subprocess.Popen(['git', 'ls-files'], cwd=SOURCE_DIRECTORY,
stdout=subprocess.PIPE)
output = ls_files.communicate()[0].decode('utf-8')
for filename in output.splitlines():
if not any(re.search(p, filename) for p in REQUIRED_BLACKLIST):
yield filename
def _get_manifest_files():
filelist = FileList()
old_wd = os.getcwd()
try:
os.chdir(SOURCE_DIRECTORY)
filelist.findall()
finally:
os.chdir(old_wd)
with open(MANIFEST, 'r', encoding='utf-8') as source:
for line in source:
filelist.process_template_line(line.strip())
filelist.process_template_line('prune .tox')
return filelist.files
def trim_docstring(text):
"""
Trim indentation and blank lines from docstring text & return it.
See PEP 257.
"""
if not text:
return text
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = text.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
ALLOWED_SCHEMES = '''file ftp gopher hdl http https imap mailto mms news nntp
prospero rsync rtsp rtspu sftp shttp sip sips snews svn svn+ssh telnet
wais'''.split()
def render_readme_like_pypi(source, output_encoding='unicode'):
"""
Render a ReST document just like PyPI does.
"""
# Dedent all lines of `source`.
source = trim_docstring(source)
settings_overrides = {
'raw_enabled': 0, # no raw HTML code
'file_insertion_enabled': 0, # no file/URL access
'halt_level': 2, # at warnings or errors, raise an exception
'report_level': 5, # never report problems with the reST code
}
parts = None
# Convert reStructuredText to HTML using Docutils.
document = publish_doctree(source=source,
settings_overrides=settings_overrides)
for node in document.traverse():
if node.tagname == '#text':
continue
if node.hasattr('refuri'):
uri = node['refuri']
elif node.hasattr('uri'):
uri = node['uri']
else:
continue
o = urlparse(uri)
if o.scheme not in ALLOWED_SCHEMES:
raise TransformError('link scheme not allowed: {0}'.format(uri))
# now turn the transformed document into HTML
reader = readers.doctree.Reader(parser_name='null')
pub = Publisher(reader, source=io.DocTreeInput(document),
destination_class=io.StringOutput)
pub.set_writer('html')
pub.process_programmatic_settings(None, settings_overrides, None)
pub.set_destination(None, None)
pub.publish()
parts = pub.writer.parts
output = parts['body']
if output_encoding != 'unicode':
output = output.encode(output_encoding)
return output
def test_manifest_complete():
required_files = sorted(_get_required_files())
included_files = sorted(_get_manifest_files())
assert required_files == included_files
@pytest.mark.skipif(str('sys.version_info[0] > 2'))
def test_description_rendering():
with open(README, 'r', encoding='utf-8') as source:
output = render_readme_like_pypi(source.read())
assert output
pyudev-0.21.0/tests/utils/ 0000775 0001750 0001750 00000000000 12744231232 016560 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/tests/utils/udev.py 0000664 0001750 0001750 00000023442 12744230565 020112 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
utils.udev
=====================
Provide access the udev device database.
Parses the udev device database from :program:`udevadm`.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import sys
import os
import re
import errno
import random
import subprocess
from collections import Iterable, Sized
class UDevAdm(object):
"""
Wrap ``udevadm`` utility.
"""
CANDIDATES = ['/sbin/udevadm', 'udevadm']
_adm = None
@classmethod
def find(cls):
"""
Construct a valid :class:`UDevAdm` object.
:returns: a working :class:`UDevAdm` object
:rtype: :class:`UDevAdm`
:raises EnvironmentError:
"""
for candidate in cls.CANDIDATES:
try:
udevadm = cls(candidate)
# try to execute udev to make sure that it's actually
# executable
udevadm.query_udev_version()
return udevadm
except EnvironmentError as error:
if error.errno != errno.ENOENT:
raise
@classmethod
def adm(cls):
"""
Returns the singleton object of this class, if one can be found.
:returns: singleton :class:`UDevAdm` object
:rtype: :class:`UDevAdm`
"""
if cls._adm is None:
try:
cls._adm = cls.find()
except EnvironmentError:
pass
return cls._adm
def __init__(self, udevadm):
"""
Create a new ``udevadm`` wrapper for the given udevadm executable.
``udevadm`` is the path to udevadm as string. If relative, ``udevadm``
is looked up in ``$PATH``.
"""
self.udevadm = udevadm
def query_udev_version(self):
"""
Return the version of udevadm.
:returns: the udevadm version
:rtype: int
"""
return int(self._execute('--version'))
def _execute(self, *args):
"""
Execute a udevadm command.
:raises CalledProcessError:
"""
command = [self.udevadm] + list(args)
proc = subprocess.Popen(command, stdout=subprocess.PIPE)
output = proc.communicate()[0].strip()
if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, command)
return output
def _execute_query(self, device_path, query_type='all'):
"""
Execute a "udevadm info" query.
:raises CalledProcessError:
"""
output = self._execute('info', '--root', '--path', device_path,
'--query', query_type)
return output.decode(sys.getfilesystemencoding())
def query_devices(self):
"""
Generate devices from udevadm database.
Yields sys paths, minus the initial '/sys'.
:raises CalledProcessError:
"""
database = self._execute('info', '--export-db').decode(
sys.getfilesystemencoding()).splitlines()
return (l[3:] for l in (l.strip() for l in database) if l[:3] == "P: ")
def query_device_properties(self, device_path):
"""
Return map of properties.
:returns: a map of properties on the device
:rtype: dict of str * str
:raises CalledProcessError:
"""
pairs = [
l.strip().split('=', 1) for l in \
self._execute_query(device_path, 'property').splitlines()
]
if self.adm().query_udev_version() < 230:
num_pairs = len(pairs)
indices = [i for i in range(num_pairs) if pairs[i][1] == ""]
pairs = [pairs[i] for i in range(num_pairs) if \
i not in indices and i - 1 not in indices]
return dict(pairs)
def query_device_attributes(self, device_path):
output = self._execute(
'info', '--attribute-walk', '--path', device_path)
attribute_dump = output.decode(
sys.getfilesystemencoding()).splitlines()
attributes = {}
for line in attribute_dump:
line = line.strip()
if line.startswith('looking at parent device'):
# we don't continue with attributes of parent devices, we only
# want the attributes of the given device
break
if line.startswith('ATTR'):
name, value = line.split('==', 1)
# remove quotation marks from attribute value
value = value[1:-1]
# remove prefix from attribute name
name = re.search('{(.*)}', name).group(1)
attributes[name] = value
return attributes
def query_device(self, device_path, query_type):
if query_type not in ('symlink', 'name'):
raise ValueError(query_type)
try:
return self._execute_query(device_path, query_type)
except subprocess.CalledProcessError:
return None
class DeviceData(object):
"""
Data for a single device.
"""
def __init__(self, device_path, udevadm):
self.device_path = device_path
self._udevadm = udevadm
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, self.device_path)
@property
def sys_path(self):
"""
Get the ``sysfs`` path of the device.
"""
return '/sys' + self.device_path
@property
def exists(self):
"""
Whether this device has some real existance on machine.
:returns: True if the device does exist, otherwise False.
:rtype: bool
"""
return os.path.exists(self.sys_path)
@property
def properties(self):
"""
Get the device properties as mapping of property names as strings to
property values as strings.
"""
return self._udevadm.query_device_properties(self.device_path)
@property
def attributes(self):
"""
Get the device attributes as mapping of attributes names as strings to
property values as byte strings.
.. warning::
As ``udevadm`` only exports printable attributes, this list can be
incomplete! Do *not* compare this dictionary directly to a
attributes dictionary received through pyudev!
"""
return self._udevadm.query_device_attributes(self.device_path)
@property
def tags(self):
"""
Get the device tags as list of strings.
"""
tags = self.properties.get('TAGS', '').split(':')
return [t for t in tags if t]
@property
def device_node(self):
"""
Get the device node path as string, or ``None`` if the device has no
device node.
"""
return self._udevadm.query_device(self.device_path, 'name')
@property
def device_links(self):
"""
Get the device links as list of strings.
"""
links = self._udevadm.query_device(self.device_path, 'symlink')
return links.split() if links else []
@property
def device_number(self):
"""
Get the device number as integer or 0 if the device has no device
number.
"""
if self.device_node:
return os.stat(self.device_node).st_rdev
else:
return 0
class DeviceDatabase(Iterable, Sized):
"""
The udev device database.
Takes a snapshot of the udev device database when it is initialized.
Consequently, some :class:`DeviceData` objects in the database may
not exist when the database is iterated over.
This class is an iterable over :class:`DeviceData` objects that contain the
data associated with each device stored in the udev database.
"""
_db = None
@classmethod
def db(cls, renew=False): # pylint: disable=invalid-name
"""
Get a database object.
:param bool renew: if renew is True, get a new object
:returns: a database object
:rtype: :class:`DeviceDatabase`
"""
if cls._db is None or renew:
udevadm = UDevAdm.adm()
if udevadm:
cls._db = DeviceDatabase(udevadm)
return cls._db
def __init__(self, udevadm):
self._udevadm = udevadm
self._devices = set(self._udevadm.query_devices())
def __iter__(self):
return (
d for d in (DeviceData(d, self._udevadm) for d in self._devices) \
if d.exists
)
def __len__(self):
return sum(1 for _ in self)
def find_device_data(self, device_path):
"""
Find device data for the device designated by ``device_path``.
``device_path`` is a string containing the device path.
Return a :class:`DeviceData` object containing the data for the given
device, or ``None`` if the device was not found.
"""
data = DeviceData(device_path, self._udevadm)
return data if data.exists and data in self else None
pyudev-0.21.0/tests/utils/__init__.py 0000664 0001750 0001750 00000002037 12744230565 020703 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.tests.utils
==========================
Utils to support pyudev testing.
.. moduleauthor:: mulhern
"""
from . import udev
from .misc import failed_health_check_wrapper
from .misc import is_unicode_string
pyudev-0.21.0/tests/utils/misc.py 0000664 0001750 0001750 00000004033 12744230565 020075 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
utils.misc
=============
Miscellaneous useful methods.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from functools import wraps
import six
import pytest
from hypothesis.core import FailedHealthCheck
def is_unicode_string(value):
"""
Return ``True``, if ``value`` is of a real unicode string type
(``unicode`` in python 2, ``str`` in python 3), ``False`` otherwise.
"""
return isinstance(value, unicode if six.PY2 else str)
def failed_health_check_wrapper(func):
"""
If the test fails a health check, calls skip().
"""
@wraps(func)
def the_func(*args):
"""
Catch a hypothesis FailedHealthCheck exception and log it as a skip.
"""
try:
func(*args)
except FailedHealthCheck:
func_code = six.get_function_code(func)
pytest.skip(
'failed health check for %s() (%s: %s)' % \
(
func_code.co_name,
func_code.co_filename,
func_code.co_firstlineno
)
)
return the_func
pyudev-0.21.0/tests/__init__.py 0000664 0001750 0001750 00000000000 12654153421 017522 0 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/tests/test_device.py 0000664 0001750 0001750 00000005411 12660444030 020270 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.tests.test_device
========================
Test for devices.
.. moduleauthor:: mulhern
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import gc
import pytest
from ._constants import _DEVICE_DATA
# pylint: disable=too-few-public-methods
if len(_DEVICE_DATA) > 0:
# pylint: disable=unused-import
from ._device_tests._devices_tests import TestDevices
else:
class TestDevices(object):
""" Not enough devices available. """
def test_all(self):
""" Always skipped test. """
pytest.skip("skipping all devices tests, not enough devices")
if len(_DEVICE_DATA) > 0:
# pylint: disable=unused-import
from ._device_tests._device_tests import TestDevice
else:
class TestDevice(object):
""" Not enough devices available. """
def test_all(self):
""" Always skipped test. """
pytest.skip("skipping all device tests, not enough devices")
if len(_DEVICE_DATA) > 0:
# pylint: disable=unused-import
from ._device_tests._attributes_tests import TestAttributes
else:
class TestAttributes(object):
""" Not enough devices available. """
def test_all(self):
""" Always skipped test. """
pytest.skip("skipping all attributes tests, not enough devices")
if len(_DEVICE_DATA) > 0:
# pylint: disable=unused-import
from ._device_tests._tags_tests import TestTags
else:
class TestTags(object):
""" Not enough devices available. """
def test_all(self):
""" Always skipped test. """
pytest.skip("skipping all tags tests, not enough devices")
def test_garbage():
"""
Make sure that all the device tests create no uncollectable objects.
This test must stick at the bottom of this test file to make sure that
``py.test`` always executes it at last.
"""
assert not gc.garbage
pyudev-0.21.0/tests/test_core.py 0000664 0001750 0001750 00000006546 12710747516 020006 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import random
import syslog
import mock
from pyudev import udev_version
from tests._constants import _UDEV_TEST
from tests.utils import is_unicode_string
def test_udev_version():
assert isinstance(udev_version(), int)
# just to make sure, that udev versioning works. pyudev itself should be
# compatible with earlier versions of pyudev. However, 150 is currently
# the earliest udev release, I'm testing against (using Ubuntu 10.04)
assert udev_version() > 150
class TestContext(object):
def test_sys_path(self, context):
assert is_unicode_string(context.sys_path)
assert context.sys_path == '/sys'
def test_device_path(self, context):
assert is_unicode_string(context.device_path)
assert context.device_path == '/dev'
@_UDEV_TEST(167, "test_run_path")
def test_run_path(self, context):
assert is_unicode_string(context.run_path)
assert context.run_path == '/run/udev'
def test_log_priority_get(self, context):
assert isinstance(context.log_priority, int)
assert syslog.LOG_EMERG <= context.log_priority <= syslog.LOG_DEBUG
def test_log_priority_get_mock(self, context):
spec = lambda c: None
funcname = 'udev_get_log_priority'
with mock.patch.object(context._libudev, funcname,
autospec=spec) as func:
func.return_value = mock.sentinel.log_priority
assert context.log_priority is mock.sentinel.log_priority
func.assert_called_once_with(context)
def test_log_priority_set_mock(self, context):
spec = lambda c, p: None
funcname = 'udev_set_log_priority'
with mock.patch.object(context._libudev, funcname,
autospec=spec) as func:
context.log_priority = mock.sentinel.log_priority
func.assert_called_once_with(context, mock.sentinel.log_priority)
def test_log_priority_roundtrip(self, context):
# FIXME: This adds UDEV_LOG properties?!
old_priority = context.log_priority
available_levels = [
l for l in range(syslog.LOG_EMERG, syslog.LOG_DEBUG + 1)
if l != old_priority]
new_priority = random.choice(available_levels)
assert new_priority != old_priority
try:
context.log_priority = new_priority
assert context.log_priority == new_priority
finally:
context.log_priority = old_priority
pyudev-0.21.0/tests/conftest.py 0000664 0001750 0001750 00000002506 12710747516 017634 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import pytest
import pyudev
pytest_plugins = [
str('tests.plugins.fake_monitor'),
str('tests.plugins.privileged'),
str('tests.plugins.mock_libudev'),
str('tests.plugins.travis'),
]
@pytest.fixture
def context(request):
"""
Return a useable :class:`pyudev.Context` object.
"""
try:
return pyudev.Context()
except ImportError:
pytest.skip('udev not available')
pyudev-0.21.0/tests/test_observer_deprecated.py 0000664 0001750 0001750 00000022617 12744230565 023060 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import pytest
import mock
from pyudev import Monitor, Devices
@pytest.fixture
def monitor(request):
return Monitor.from_netlink(request.getfuncargvalue('context'))
@pytest.fixture
def fake_monitor_device(request):
context = request.getfuncargvalue('context')
return Devices.from_path(context, '/devices/platform')
ACTIONS = ('add', 'remove', 'change', 'move')
class DeprecatedObserverTestBase(object):
def setup_method(self, method):
self.observer = None
self.no_emitted_signals = 0
self.setup()
def teardown_method(self, method):
if self.observer is not None:
self.destroy_observer()
self.teardown()
def setup(self):
pass
def teardown(self):
pass
def destroy_observer(self):
self.observer.enabled = False
def create_observer(self, monitor):
raise NotImplementedError()
def create_event_loop(self, self_stop_timeout=5000):
raise NotImplementedError()
def start_event_loop(self, start_callback):
raise NotImplementedError()
def stop_event_loop(self):
raise NotImplementedError()
def connect_signal(self, callback, action=None):
raise NotImplementedError()
def stop_when_done(self, *args, **kwargs):
self.no_emitted_signals += 1
if self.no_emitted_signals >= 2:
self.stop_event_loop()
def prepare_test(self, monitor):
self.create_event_loop(self_stop_timeout=5000)
self.create_observer(monitor)
def test_monitor(self, fake_monitor):
self.prepare_test(fake_monitor)
# test that the monitor attribute is correct
assert self.observer.monitor is fake_monitor
@pytest.mark.parametrize('action', ACTIONS, ids=ACTIONS)
def test_events_fake_monitor(self, action, fake_monitor,
fake_monitor_device):
self.prepare_test(fake_monitor)
event_callback = mock.Mock(side_effect=self.stop_when_done)
action_callback = mock.Mock(side_effect=self.stop_when_done)
self.connect_signal(event_callback)
self.connect_signal(action_callback, action=action)
funcname = 'udev_device_get_action'
spec = lambda d: None
with mock.patch.object(fake_monitor_device._libudev, funcname,
autospec=spec) as func:
func.return_value = action.encode('ascii')
self.start_event_loop(fake_monitor.trigger_event)
func.assert_called_with(fake_monitor_device)
event_callback.assert_called_with(action, fake_monitor_device)
action_callback.assert_called_with(fake_monitor_device)
@pytest.mark.privileged
def test_events_real(self, context, monitor):
# make sure that the module is unloaded initially
pytest.unload_dummy()
monitor.filter_by('net')
monitor.start()
self.prepare_test(monitor)
# setup signal handlers
event_callback = mock.Mock(side_effect=self.stop_when_done)
added_callback = mock.Mock(side_effect=self.stop_when_done)
removed_callback = mock.Mock(side_effect=self.stop_when_done)
self.connect_signal(event_callback)
self.connect_signal(added_callback, action='add')
self.connect_signal(removed_callback, action='remove')
# test add event
self.start_event_loop(pytest.load_dummy)
device = Devices.from_path(context, '/devices/virtual/net/dummy0')
event_callback.assert_called_with('add', device)
added_callback.assert_called_with(device)
assert not removed_callback.called
for callback in (event_callback, added_callback, removed_callback):
callback.reset_mock()
self.start_event_loop(pytest.unload_dummy)
event_callback.assert_called_with('remove', device)
assert not added_callback.called
removed_callback.assert_called_with(device)
class DeprecatedQtObserverTestBase(DeprecatedObserverTestBase):
ACTION_SIGNAL_MAP = {
'add': 'deviceAdded',
'remove': 'deviceRemoved',
'change': 'deviceChanged',
'move': 'deviceMoved',
}
def setup(self):
self.qtcore = pytest.importorskip('{0}.QtCore'.format(
self.BINDING_NAME))
def create_observer(self, monitor):
name = self.BINDING_NAME.lower()
mod = __import__('pyudev.{0}'.format(name), None, None, [name])
self.observer = mod.QUDevMonitorObserver(monitor)
def connect_signal(self, callback, action=None):
if action is None:
self.observer.deviceEvent.connect(callback)
else:
signal = getattr(self.observer, self.ACTION_SIGNAL_MAP[action])
signal.connect(callback)
def create_event_loop(self, self_stop_timeout=5000):
self.app = self.qtcore.QCoreApplication.instance()
if not self.app:
self.app = self.qtcore.QCoreApplication([])
self.qtcore.QTimer.singleShot(
self_stop_timeout, self.stop_event_loop)
def start_event_loop(self, start_callback):
self.qtcore.QTimer.singleShot(0, start_callback)
self.app.exec_()
def stop_event_loop(self):
self.app.quit()
class TestDeprecatedPysideObserver(DeprecatedQtObserverTestBase):
BINDING_NAME = 'PySide'
class TestDeprecatedPyQt4Observer(DeprecatedQtObserverTestBase):
BINDING_NAME = 'PyQt4'
class TestDeprecatedGlibObserver(DeprecatedObserverTestBase):
ACTION_SIGNAL_MAP = {
'add': 'device-added',
'remove': 'device-removed',
'change': 'device-changed',
'move': 'device-moved',
}
def setup(self):
self.event_sources = []
self.glib = pytest.importorskip('glib')
# make sure that we also have gobject
pytest.importorskip('gobject')
def teardown(self):
for source in self.event_sources:
self.glib.source_remove(source)
def create_observer(self, monitor):
from pyudev.glib import GUDevMonitorObserver
self.observer = GUDevMonitorObserver(monitor)
def connect_signal(self, callback, action=None):
# drop the sender argument from glib signal connections
def _wrapper(obj, *args, **kwargs):
return callback(*args, **kwargs)
if action is None:
self.observer.connect('device-event', _wrapper)
else:
self.observer.connect(self.ACTION_SIGNAL_MAP[action], _wrapper)
def create_event_loop(self, self_stop_timeout=5000):
self.mainloop = self.glib.MainLoop()
self.event_sources.append(
self.glib.timeout_add(self_stop_timeout, self.stop_event_loop))
def start_event_loop(self, start_callback):
def _wrapper(*args, **kwargs):
start_callback(*args, **kwargs)
return False
self.event_sources.append(self.glib.timeout_add(0, _wrapper))
self.mainloop.run()
def stop_event_loop(self):
self.mainloop.quit()
return False
@pytest.mark.skipif(str('"DISPLAY" not in os.environ'),
reason='Display required for wxPython')
class TestDeprecatedWxObserver(DeprecatedObserverTestBase):
def setup(self):
self.wx = pytest.importorskip('wx')
def create_observer(self, monitor):
from pyudev import wx
self.observer = wx.WxUDevMonitorObserver(monitor)
self.action_event_map = {
'add': wx.EVT_DEVICE_ADDED,
'remove': wx.EVT_DEVICE_REMOVED,
'change': wx.EVT_DEVICE_CHANGED,
'move': wx.EVT_DEVICE_MOVED
}
def connect_signal(self, callback, action=None):
if action is None:
from pyudev.wx import EVT_DEVICE_EVENT
def _wrapper(event):
return callback(event.action, event.device)
self.observer.Bind(EVT_DEVICE_EVENT, _wrapper)
else:
def _wrapper(event):
return callback(event.device)
self.observer.Bind(self.action_event_map[action], _wrapper)
def create_event_loop(self, self_stop_timeout=5000):
self.app = self.wx.App(False)
# need to create a dummy Frame to get the mainloop running. Praise the
# broken wx API…
self.app.frame = self.wx.Frame(None)
timer = self.wx.PyTimer(self.stop_event_loop)
timer.Start(self_stop_timeout, True)
def start_event_loop(self, start_callback):
timer = self.wx.PyTimer(start_callback)
timer.Start(0, True)
self.app.MainLoop()
def stop_event_loop(self):
self.app.Exit()
pyudev-0.21.0/tests/test_observer.py 0000664 0001750 0001750 00000016606 12744230565 020701 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import random
import pytest
import mock
from pyudev import Monitor, Devices
from tests.utils.udev import DeviceDatabase
@pytest.fixture
def monitor(request):
return Monitor.from_netlink(request.getfuncargvalue('context'))
@pytest.fixture
def fake_monitor_device(request):
context = request.getfuncargvalue('context')
device = random.choice(list(DeviceDatabase.db()))
return Devices.from_path(context, device.device_path)
def test_fake_monitor(fake_monitor, fake_monitor_device):
"""
Test the fake monitor just to make sure, that it works.
"""
assert fake_monitor.poll(timeout=0) is None
fake_monitor.trigger_event()
device = fake_monitor.poll()
assert device == fake_monitor_device
class ObserverTestBase(object):
def setup_method(self, method):
self.observer = None
self.no_emitted_signals = 0
self.setup()
def teardown_method(self, method):
if self.observer is not None:
self.destroy_observer()
self.teardown()
def setup(self):
pass
def teardown(self):
pass
def destroy_observer(self):
self.observer.enabled = False
def create_observer(self, monitor):
raise NotImplementedError()
def create_event_loop(self, self_stop_timeout=5000):
raise NotImplementedError()
def start_event_loop(self, start_callback):
raise NotImplementedError()
def stop_event_loop(self):
raise NotImplementedError()
def connect_signal(self, callback, action=None):
raise NotImplementedError()
def prepare_test(self, monitor):
self.create_event_loop(self_stop_timeout=5000)
self.create_observer(monitor)
def test_monitor(self, fake_monitor):
self.prepare_test(fake_monitor)
# test that the monitor attribute is correct
assert self.observer.monitor is fake_monitor
def test_events_fake_monitor(self, fake_monitor, fake_monitor_device):
self.prepare_test(fake_monitor)
event_callback = mock.Mock(
side_effect=lambda *args: self.stop_event_loop())
self.connect_signal(event_callback)
self.start_event_loop(fake_monitor.trigger_event)
event_callback.assert_called_with(fake_monitor_device)
@pytest.mark.privileged
def test_events_real(self, context, monitor):
# make sure that the module is unloaded initially
pytest.unload_dummy()
monitor.filter_by('net')
monitor.start()
self.prepare_test(monitor)
# setup signal handlers
event_callback = mock.Mock(
side_effect=lambda *args: self.stop_event_loop())
self.connect_signal(event_callback)
# test add event
self.start_event_loop(pytest.load_dummy)
device = Devices.from_path(context, '/devices/virtual/net/dummy0')
event_callback.assert_called_with(device)
event_callback.reset_mock()
self.start_event_loop(pytest.unload_dummy)
event_callback.assert_called_with(device)
class QtObserverTestBase(ObserverTestBase):
def setup(self):
self.qtcore = pytest.importorskip('{0}.QtCore'.format(
self.BINDING_NAME))
def create_observer(self, monitor):
name = self.BINDING_NAME.lower()
mod = __import__('pyudev.{0}'.format(name), None, None, [name])
self.observer = mod.MonitorObserver(monitor)
def connect_signal(self, callback):
self.observer.deviceEvent.connect(callback)
def create_event_loop(self, self_stop_timeout=5000):
self.app = self.qtcore.QCoreApplication.instance()
if not self.app:
self.app = self.qtcore.QCoreApplication([])
self.qtcore.QTimer.singleShot(
self_stop_timeout, self.stop_event_loop)
def start_event_loop(self, start_callback):
self.qtcore.QTimer.singleShot(0, start_callback)
self.app.exec_()
def stop_event_loop(self):
self.app.quit()
class TestPysideObserver(QtObserverTestBase):
BINDING_NAME = 'PySide'
class TestPyQt4Observer(QtObserverTestBase):
BINDING_NAME = 'PyQt4'
class TestPyQt5Observer(QtObserverTestBase):
BINDING_NAME = 'PyQt5'
class TestGlibObserver(ObserverTestBase):
def setup(self):
self.event_sources = []
self.glib = pytest.importorskip('glib')
# make sure that we also have gobject
pytest.importorskip('gobject')
def teardown(self):
for source in self.event_sources:
self.glib.source_remove(source)
def create_observer(self, monitor):
from pyudev.glib import MonitorObserver
self.observer = MonitorObserver(monitor)
def connect_signal(self, callback):
# drop the sender argument from glib signal connections
def _wrapper(obj, *args, **kwargs):
return callback(*args, **kwargs)
self.observer.connect('device-event', _wrapper)
def create_event_loop(self, self_stop_timeout=5000):
self.mainloop = self.glib.MainLoop()
self.event_sources.append(
self.glib.timeout_add(self_stop_timeout, self.stop_event_loop))
def start_event_loop(self, start_callback):
def _wrapper(*args, **kwargs):
start_callback(*args, **kwargs)
return False
self.event_sources.append(self.glib.timeout_add(0, _wrapper))
self.mainloop.run()
def stop_event_loop(self):
self.mainloop.quit()
return False
@pytest.mark.skipif(str('"DISPLAY" not in os.environ'),
reason='Display required for wxPython')
class TestWxObserver(ObserverTestBase):
def setup(self):
self.wx = pytest.importorskip('wx')
def create_observer(self, monitor):
from pyudev import wx
self.observer = wx.MonitorObserver(monitor)
def connect_signal(self, callback):
from pyudev.wx import EVT_DEVICE_EVENT
def _wrapper(event):
return callback(event.device)
self.observer.Bind(EVT_DEVICE_EVENT, _wrapper)
def create_event_loop(self, self_stop_timeout=5000):
self.app = self.wx.App(False)
# need to create a dummy Frame to get the mainloop running. Praise the
# broken wx API…
self.app.frame = self.wx.Frame(None)
timer = self.wx.PyTimer(self.stop_event_loop)
timer.Start(self_stop_timeout, True)
def start_event_loop(self, start_callback):
timer = self.wx.PyTimer(start_callback)
timer.Start(0, True)
self.app.MainLoop()
def stop_event_loop(self):
self.app.Exit()
pyudev-0.21.0/tests/_device_tests/ 0000775 0001750 0001750 00000000000 12744231232 020240 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/tests/_device_tests/_tags_tests.py 0000664 0001750 0001750 00000006304 12744230565 023144 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
Tests methods belonging to Devices class.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
import pytest
import mock
from pyudev import Devices
from ..utils import is_unicode_string
from ._device_tests import _CONTEXT_STRATEGY
from ._device_tests import _DEVICE_DATA
from ._device_tests import _DEVICES
from ._device_tests import _UDEV_TEST
class TestTags(object):
"""
Test methods of the ``Tags`` class.
"""
pytestmark = _UDEV_TEST(154, "TestTags")
_device_data = [d for d in _DEVICE_DATA if d.tags]
@pytest.mark.skipif(
len(_device_data) == 0,
reason="no device with tags"
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_iteration_and_contains(self, a_context, device_datum):
"""
Test that iteration yields all tags and contains checks them.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assert frozenset(device.tags) == frozenset(device_datum.tags)
assert all(is_unicode_string(tag) for tag in device.tags)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_iteration_mock(self, a_device):
funcname = 'udev_device_get_tags_list_entry'
with pytest.libudev_list(a_device._libudev, funcname,
[b'spam', b'eggs']):
tags = list(a_device.tags)
assert tags == ['spam', 'eggs']
func = a_device._libudev.udev_device_get_tags_list_entry
func.assert_called_once_with(a_device)
@_UDEV_TEST(172, "test_contans_mock")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_contains_mock(self, a_device):
"""
Test that ``udev_device_has_tag`` is called if available.
"""
funcname = 'udev_device_has_tag'
spec = lambda d, t: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as func:
func.return_value = 1
assert 'foo' in a_device.tags
func.assert_called_once_with(a_device, b'foo')
pyudev-0.21.0/tests/_device_tests/_device_tests.py 0000664 0001750 0001750 00000050016 12744230565 023444 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
Tests methods belonging to Device class.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import re
import os
import operator
import gc
from datetime import timedelta
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
import pytest
import mock
from pyudev import Device
from pyudev import Devices
from pyudev.device import Attributes, Tags
from ..utils import is_unicode_string
from .._constants import _CONTEXT_STRATEGY
from .._constants import _DEVICE_DATA
from .._constants import _DEVICES
from .._constants import _UDEV_TEST
class TestDevice(object):
"""
Test ``Device`` methods.
"""
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_parent(self, a_device):
assert a_device.parent is None or \
isinstance(a_device.parent, Device)
_devices = [d for d in _DEVICES if d.parent is not None]
@pytest.mark.skipif(len(_devices) == 0, reason='no device with a parent')
@_UDEV_TEST(172, "test_child_of_parents")
@given(strategies.sampled_from(_devices))
@settings(max_examples=5, min_satisfying_examples=1)
def test_child_of_parent(self, a_device):
assert a_device in a_device.parent.children
_devices = [d for d in _DEVICES if list(d.children)]
@pytest.mark.skipif(len(_devices) == 0, reason='no device with a child')
@_UDEV_TEST(172, "test_children")
@given(strategies.sampled_from(_devices))
@settings(max_examples=5, min_satisfying_examples=1)
def test_children(self, a_device):
children = list(a_device.children)
for child in children:
assert child != a_device
assert a_device in child.ancestors
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_ancestors(self, a_device):
child = a_device
for ancestor in a_device.ancestors:
assert ancestor == child.parent
child = ancestor
_devices = [d for d in _DEVICES if d.find_parent(d.subsystem) is not None]
@pytest.mark.skipif(
len(_devices) == 0,
reason='no device with a parent in the same subsystem'
)
@given(strategies.sampled_from(_devices))
@settings(max_examples=5, min_satisfying_examples=1)
def test_find_parent(self, a_device):
parent = a_device.find_parent(a_device.subsystem)
assert parent.subsystem == a_device.subsystem
assert parent in a_device.ancestors
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_find_parent_no_devtype_mock(self, a_device):
funcname = 'udev_device_get_parent_with_subsystem_devtype'
spec = lambda d, s, t: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as get_parent:
get_parent.return_value = mock.sentinel.parent_device
funcname = 'udev_device_ref'
spec = lambda d: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as device_ref:
device_ref.return_value = mock.sentinel.referenced_device
parent = a_device.find_parent('subsystem')
assert isinstance(parent, Device)
assert parent._as_parameter_ is mock.sentinel.referenced_device
get_parent.assert_called_once_with(
a_device,
b'subsystem',
None
)
device_ref.assert_called_once_with(
mock.sentinel.parent_device
)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_find_parent_with_devtype_mock(self, a_device):
funcname = 'udev_device_get_parent_with_subsystem_devtype'
spec = lambda d, s, t: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as get_parent:
get_parent.return_value = mock.sentinel.parent_device
funcname = 'udev_device_ref'
spec = lambda d: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as device_ref:
device_ref.return_value = mock.sentinel.referenced_device
parent = a_device.find_parent('subsystem', 'devtype')
assert isinstance(parent, Device)
assert parent._as_parameter_ is mock.sentinel.referenced_device
get_parent.assert_called_once_with(
a_device, b'subsystem', b'devtype')
device_ref.assert_called_once_with(
mock.sentinel.parent_device
)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_traverse(self, a_device):
child = a_device
for parent in pytest.deprecated_call(a_device.traverse):
assert parent == child.parent
child = parent
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_sys_path(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.sys_path == device_datum.sys_path
assert is_unicode_string(device.sys_path)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_device_path(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.device_path == device_datum.device_path
assert is_unicode_string(device.device_path)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_subsystem(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.subsystem == device_datum.properties['SUBSYSTEM']
assert is_unicode_string(device.subsystem)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_device_sys_name(self, a_device):
assert a_device.sys_name.replace('/', '!') == \
os.path.basename(a_device.device_path)
assert is_unicode_string(a_device.sys_name)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_sys_number(self, a_device):
match = re.search(r'\d+$', a_device.sys_name)
# filter out devices with completely nummeric names (first character
# doesn't count according to the implementation of libudev)
if match and match.start() > 1:
assert a_device.sys_number == match.group(0)
assert is_unicode_string(a_device.sys_name)
else:
assert a_device.sys_number is None
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_type(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.device_type == device_datum.properties.get('DEVTYPE')
if device.device_type:
assert is_unicode_string(device.device_type)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_driver(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.driver == device_datum.properties.get('DRIVER')
if device.driver:
assert is_unicode_string(device.driver)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_device_node(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.device_node == device_datum.device_node
if device.device_node:
assert is_unicode_string(device.device_node)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_device_number(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device.device_number == device_datum.device_number
@_UDEV_TEST(165, "test_is_initialized")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_is_initialized(self, a_device):
assert isinstance(a_device.is_initialized, bool)
@_UDEV_TEST(165, "test_is_initialized_mock")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_is_initialized_mock(self, a_device):
funcname = 'udev_device_get_is_initialized'
spec = lambda d: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as func:
func.return_value = False
assert not a_device.is_initialized
func.assert_called_once_with(a_device)
@_UDEV_TEST(165, "test_time_since_initialized")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_time_since_initialized(self, a_device):
assert isinstance(a_device.time_since_initialized, timedelta)
@_UDEV_TEST(165, "test_time_since_initialized_mock")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_time_since_initialized_mock(self, a_device):
funcname = 'udev_device_get_usec_since_initialized'
spec = lambda d: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as func:
func.return_value = 100
assert a_device.time_since_initialized.microseconds == 100
func.assert_called_once_with(a_device)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_links(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert sorted(device.device_links) == sorted(device_datum.device_links)
for link in device.device_links:
assert is_unicode_string(link)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_action(self, a_device):
assert a_device.action is None
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_action_mock(self, a_device):
funcname = 'udev_device_get_action'
spec = lambda d: None
with mock.patch.object(a_device._libudev, funcname,
autospec=spec) as func:
func.return_value = b'spam'
assert a_device.action == 'spam'
func.assert_called_once_with(a_device)
func.reset_mock()
assert is_unicode_string(a_device.action)
func.assert_called_once_with(a_device)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_sequence_number(self, a_device):
assert a_device.sequence_number == 0
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_attributes(self, a_device):
# see TestAttributes for complete attribute tests
assert isinstance(a_device.attributes, Attributes)
@given(_CONTEXT_STRATEGY)
def test_no_leak(self, a_context):
"""
Regression test for issue #32, modelled after the script which revealed
this issue.
The leak was caused by the following reference cycle between
``Attributes`` and ``Device``:
Device._attributes -> Attributes.device
https://github.com/lunaryorn/pyudev/issues/32
"""
for _ in a_context.list_devices(subsystem='usb'):
pass
# make sure that no memory leaks
assert not gc.garbage
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_tags(self, a_device):
# see TestTags for complete tag tests
assert isinstance(a_device.tags, Tags)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_iteration(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
for prop in device.properties:
assert is_unicode_string(prop)
# test that iteration really yields all properties
device_properties = set(device.properties)
for prop in device_datum.properties:
assert prop in device_properties
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=100)
@_UDEV_TEST(230, "check exact equivalence of device_datum and device")
def test_length(self, a_context, device_datum):
"""
Verify that the keys in the device and in the datum are equal.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assert frozenset(device_datum.properties.keys()) == \
frozenset(device.properties.keys())
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=100)
def test_key_subset(self, a_context, device_datum):
"""
Verify that the device contains all the keys in the device datum.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assert frozenset(device_datum.properties.keys()) <= \
frozenset(device.properties.keys())
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=100)
def test_getitem(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
for prop in device_datum.properties:
if prop == 'DEVLINKS':
assert sorted(device.properties[prop].split(),) == \
sorted(device_datum.properties[prop].split(),)
elif prop == 'TAGS':
assert sorted(device.properties[prop].split(':'),) == \
sorted(device_datum.properties[prop].split(':'),)
else:
assert device.properties[prop] == device_datum.properties[prop]
_device_data = [d for d in _DEVICE_DATA if 'DEVNAME' in d.properties]
@pytest.mark.skipif(
len(_device_data) == 0,
reason='no device with a DEVNAME property'
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_getitem_devname(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
data_devname = os.path.join(
a_context.device_path, device_datum.properties['DEVNAME'])
device_devname = \
os.path.join(a_context.device_path, device.properties['DEVNAME'])
assert device_devname == data_devname
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_getitem_nonexisting(self, a_device):
with pytest.raises(KeyError) as excinfo:
# pylint: disable=pointless-statement
a_device.properties['a non-existing property']
assert str(excinfo.value) == repr('a non-existing property')
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_asint(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
for prop, value in device_datum.properties.items():
try:
value = int(value)
except ValueError:
with pytest.raises(ValueError):
device.properties.asint(prop)
else:
assert device.properties.asint(prop) == value
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_asbool(self, a_context, device_datum):
"""
Test that values of 1 and 0 get properly interpreted as bool
and that all other values raise a ValueError.
:param Context a_context: libudev context
:param device_datum: a device datum
"""
device = Devices.from_path(a_context, device_datum.device_path)
for prop, value in device_datum.properties.items():
if value == '1':
assert device.properties.asbool(prop)
elif value == '0':
assert not device.properties.asbool(prop)
else:
with pytest.raises(ValueError) as exc_info:
device.properties.asbool(prop)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_hash(self, a_device):
assert hash(a_device) == hash(a_device.device_path)
assert hash(a_device.parent) == hash(a_device.parent)
assert hash(a_device.parent) != hash(a_device)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_equality(self, a_device):
assert a_device == a_device.device_path
assert a_device == a_device
assert a_device.parent == a_device.parent
# pylint: disable=superfluous-parens
assert not (a_device == a_device.parent)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_inequality(self, a_device):
# pylint: disable=superfluous-parens
assert not (a_device != a_device.device_path)
assert not (a_device != a_device)
assert not (a_device.parent != a_device.parent)
assert a_device != a_device.parent
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=1)
def test_device_ordering(self, a_device):
"""
Verify that devices are incomparable.
"""
operators = [operator.ge, operator.gt, operator.le, operator.lt]
for comp_op in operators:
with pytest.raises(TypeError) as exc_info:
comp_op(a_device, a_device)
assert str(exc_info.value) == 'Device not orderable'
_devices = [
d for d in _DEVICES if \
d.device_type == 'disk' and \
'ID_WWN_WITH_EXTENSION' in d.properties and \
'DM_MULTIPATH_TIMESTAMP' not in d.properties
]
@pytest.mark.skipif(
len(_devices) == 0,
reason='unsafe to check ID_WWN_WITH_EXTENSION'
)
@given(strategies.sampled_from(_devices))
@settings(max_examples=5, min_satisfying_examples=1)
def test_id_wwn_with_extension(self, a_device):
"""
Test that the ID_WWN_WITH_EXTENSION has a corresponding link.
Assert that the device is a block device if it has an
ID_WWN_WITH_EXTENSION property.
Skip any multipathed paths, see:
https://bugzilla.redhat.com/show_bug.cgi?id=1263441.
"""
id_wwn = a_device.properties['ID_WWN_WITH_EXTENSION']
assert a_device.subsystem == u'block'
id_path = '/dev/disk/by-id'
link_name = "wwn-%s" % id_wwn
match = next(
(d for d in os.listdir(id_path) if d == link_name),
None
)
assert match is not None
link_path = os.path.join(id_path, match)
link_target = os.readlink(link_path)
target_path = os.path.normpath(os.path.join(id_path, link_target))
assert target_path == a_device.device_node
pyudev-0.21.0/tests/_device_tests/__init__.py 0000664 0001750 0001750 00000001661 12660444030 022354 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.tests._device_tests
==========================
Tests on devices.
.. moduleauthor:: mulhern
"""
pyudev-0.21.0/tests/_device_tests/_devices_tests.py 0000664 0001750 0001750 00000023323 12744230565 023630 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
Tests methods belonging to Devices class.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import stat
from hypothesis import assume
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
import pytest
from pyudev import Devices
from pyudev import DeviceNotFoundAtPathError
from pyudev import DeviceNotFoundByFileError
from pyudev import DeviceNotFoundByNameError
from pyudev import DeviceNotFoundByNumberError
from pyudev import DeviceNotFoundInEnvironmentError
from .._constants import _CONTEXT_STRATEGY
from .._constants import _DEVICE_DATA
from .._constants import _DEVICES
from .._constants import _UDEV_TEST
class TestDevices(object):
"""
Test ``Devices`` methods.
"""
# pylint: disable=invalid-name
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_from_path(self, a_context, device_datum):
device = Devices.from_path(a_context, device_datum.device_path)
assert device is not None
assert device == \
Devices.from_sys_path(a_context, device_datum.sys_path)
assert device == Devices.from_path(a_context, device_datum.sys_path)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_from_path_strips_leading_slash(self, a_context, device_datum):
path = device_datum.device_path
assert Devices.from_path(a_context, path[1:]) == \
Devices.from_path(a_context, path)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_from_sys_path(self, a_context, device_datum):
device = Devices.from_sys_path(a_context, device_datum.sys_path)
assert device is not None
assert device.sys_path == device_datum.sys_path
@given(_CONTEXT_STRATEGY)
def test_from_sys_path_device_not_found(self, a_context):
sys_path = 'there_will_not_be_such_a_device'
with pytest.raises(DeviceNotFoundAtPathError) as exc_info:
Devices.from_sys_path(a_context, sys_path)
error = exc_info.value
assert error.sys_path == sys_path
assert str(error) == 'No device at {0!r}'.format(sys_path)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_from_name(self, a_context, a_device):
"""
Test that getting a new device based on the name and subsystem
yields an equivalent device.
"""
new_device = Devices.from_name(
a_context,
a_device.subsystem,
a_device.sys_name
)
assert new_device == a_device
@given(_CONTEXT_STRATEGY)
def test_from_name_no_device_in_existing_subsystem(self, a_context):
with pytest.raises(DeviceNotFoundByNameError) as exc_info:
Devices.from_name(a_context, 'block', 'foobar')
error = exc_info.value
assert error.subsystem == 'block'
assert error.sys_name == 'foobar'
assert str(error) == 'No device {0!r} in {1!r}'.format(
error.sys_name, error.subsystem)
@given(_CONTEXT_STRATEGY)
def test_from_name_nonexisting_subsystem(self, a_context):
with pytest.raises(DeviceNotFoundByNameError) as exc_info:
Devices.from_name(a_context, 'no_such_subsystem', 'foobar')
error = exc_info.value
assert error.subsystem == 'no_such_subsystem'
assert error.sys_name == 'foobar'
assert str(error) == 'No device {0!r} in {1!r}'.format(
error.sys_name, error.subsystem)
_device_data = [d for d in _DEVICE_DATA if d.device_node]
@pytest.mark.skipif(
len(_device_data) == 0,
reason='no device with a device node'
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_from_device_number(self, a_context, device_datum):
mode = os.stat(device_datum.device_node).st_mode
typ = 'block' if stat.S_ISBLK(mode) else 'char'
device = Devices.from_device_number(
a_context, typ, device_datum.device_number)
assert device.device_number == device_datum.device_number
# make sure, we are really referring to the same device
assert device.device_path == device_datum.device_path
_device_data = [d for d in _DEVICE_DATA if d.device_node]
@pytest.mark.skipif(
len(_device_data) == 0,
reason='no device with a device node'
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_from_device_number_wrong_type(
self,
a_context,
device_datum
):
mode = os.stat(device_datum.device_node).st_mode
# deliberately use the wrong type here to cause either failure
# or at least device mismatch
typ = 'char' if stat.S_ISBLK(mode) else 'block'
try:
# this either fails, in which case the caught exception is
# raised, or succeeds, but returns a wrong device
# (device numbers are not unique across device types)
device = Devices.from_device_number(
a_context, typ, device_datum.device_number)
# if it succeeds, the resulting device must not match the
# one, we are actually looking for!
assert device.device_path != device_datum.device_path
except DeviceNotFoundByNumberError as error:
# check the correctness of the exception attributes
assert error.device_type == typ
assert error.device_number == device_datum.device_number
@given(_CONTEXT_STRATEGY)
def test_from_device_number_invalid_type(self, a_context):
with pytest.raises(DeviceNotFoundByNumberError):
Devices.from_device_number(a_context, 'foobar', 100)
_device_data = [d for d in _DEVICE_DATA if d.device_node]
@pytest.mark.skipif(
len(_device_data) == 0,
reason='no device with a device node'
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_from_device_file(self, a_context, device_datum):
device = Devices.from_device_file(
a_context,
device_datum.device_node
)
assert device.device_node == device_datum.device_node
assert device.device_path == device_datum.device_path
_device_data = [d for d in _DEVICE_DATA if list(d.device_links)]
@pytest.mark.skipif(
len(_device_data) == 0,
reason='no device with a device node'
)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_device_data))
@settings(max_examples=5, min_satisfying_examples=1)
def test_from_device_file_links(self, a_context, device_datum):
"""
For each link in DEVLINKS, test that the constructed device's
path matches the orginal devices path.
This does not hold true in the case of multipath. In this case
udevadm's DEVLINKS fields holds some links that do not actually
point to the originating device.
See: https://bugzilla.redhat.com/show_bug.cgi?id=1263441.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assume(not 'DM_MULTIPATH_TIMESTAMP' in device.properties)
for link in device_datum.device_links:
link = os.path.join(a_context.device_path, link)
device = Devices.from_device_file(a_context, link)
assert device.device_path == device_datum.device_path
assert link in device.device_links
@given(a_context=_CONTEXT_STRATEGY)
def test_from_device_file_no_device_file(self, tmpdir, a_context):
filename = tmpdir.join('test')
filename.ensure(file=True)
with pytest.raises(DeviceNotFoundByFileError) as excinfo:
Devices.from_device_file(a_context, str(filename))
message = 'not a device file: {0!r}'.format(str(filename))
assert str(excinfo.value) == message
@given(a_context=_CONTEXT_STRATEGY)
def test_from_device_file_non_existing(self, tmpdir, a_context):
"""
Test that an OSError is raised when constructing a ``Device`` from
a file that does not actually exist.
"""
filename = tmpdir.join('test_from_device_file_non_existing')
assert not tmpdir.check(file=True)
with pytest.raises(DeviceNotFoundByFileError):
Devices.from_device_file(a_context, str(filename))
@_UDEV_TEST(152, "test_from_environment")
@given(_CONTEXT_STRATEGY)
def test_from_environment(self, a_context):
# there is no device in a standard environment
with pytest.raises(DeviceNotFoundInEnvironmentError):
Devices.from_environment(a_context)
pyudev-0.21.0/tests/_device_tests/_attributes_tests.py 0000664 0001750 0001750 00000012561 12744230565 024376 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
Tests methods belonging to Attributes class.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import stat
import sys
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
import pytest
from pyudev import Devices
from ..utils import is_unicode_string
from ._device_tests import _CONTEXT_STRATEGY
from ._device_tests import _DEVICE_DATA
from ._device_tests import _DEVICES
from ._device_tests import _UDEV_TEST
class TestAttributes(object):
"""
Test ``Attributes`` class methods.
"""
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_getitem(self, a_context, device_datum):
"""
Test that attribute value exists and is instance of bytes.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assert all(isinstance(device.attributes.get(key), bytes) \
for key in device_datum.attributes.keys())
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_getitem_nonexisting(self, a_device):
"""
Test behavior when corresponding value is non-existant.
"""
not_key = "a non-existing attribute"
assert a_device.attributes.get(not_key) is None
with pytest.raises(KeyError):
a_device.attributes.asstring(not_key)
with pytest.raises(KeyError):
a_device.attributes.asint(not_key)
with pytest.raises(KeyError):
a_device.attributes.asbool(not_key)
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_non_iterable(self, a_device):
"""
Test that the attributes object can not be iterated over.
"""
# pylint: disable=pointless-statement
with pytest.raises(TypeError):
'key' in a_device.attributes
with pytest.raises(TypeError):
a_device.attributes['key']
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_asstring(self, a_context, device_datum):
"""
Test that attribute exists for actual device and is unicode.
"""
device = Devices.from_path(a_context, device_datum.device_path)
assert all(is_unicode_string(device.attributes.asstring(key)) \
for key in device_datum.attributes.keys())
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=10)
def test_asint(self, a_context, device_datum):
"""
Test that integer result is an int or ValueError raised.
"""
device = Devices.from_path(a_context, device_datum.device_path)
for key, value in device_datum.attributes.items():
try:
value = int(value)
except ValueError:
with pytest.raises(ValueError):
device.attributes.asint(key)
@given(_CONTEXT_STRATEGY, strategies.sampled_from(_DEVICE_DATA))
@settings(max_examples=5)
def test_asbool(self, a_context, device_datum):
"""
Test that bool result is a bool or ValueError raised.
"""
device = Devices.from_path(a_context, device_datum.device_path)
for key, value in device_datum.attributes.items():
if value in ('0', '1'):
assert device.attributes.asbool(key) in (False, True)
else:
with pytest.raises(ValueError):
device.attributes.asbool(key)
@_UDEV_TEST(167, "test_available_attributes")
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=5)
def test_available_attributes(self, a_device):
"""
Test that the available attributes are exactly the names of files
in the sysfs directory that are regular files or softlinks.
"""
available_attributes = sorted(a_device.attributes.available_attributes)
attribute_filenames = []
sys_path = a_device.sys_path
for filename in sorted(os.listdir(sys_path)):
filepath = os.path.join(sys_path, filename)
status = os.lstat(filepath)
mode = status.st_mode
if not stat.S_ISLNK(mode) and not stat.S_ISREG(mode):
continue
if not stat.S_IRUSR & mode:
continue
attribute_filenames.append(filename)
assert available_attributes == attribute_filenames
pyudev-0.21.0/tests/test_enumerate.py 0000664 0001750 0001750 00000044600 12744230565 021032 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import pytest
import mock
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
from pyudev import Enumerator
from ._constants import _ATTRIBUTE_STRATEGY
from ._constants import _CONTEXT_STRATEGY
from ._constants import _DEVICE_STRATEGY
from ._constants import _MATCH_PROPERTY_STRATEGY
from ._constants import _SUBSYSTEM_STRATEGY
from ._constants import _SYSNAME_STRATEGY
from ._constants import _TAG_STRATEGY
from ._constants import _UDEV_TEST
from ._constants import _UDEV_VERSION
from .utils import failed_health_check_wrapper
def _is_int(value):
try:
int(value)
return True
except (TypeError, ValueError):
return False
def _is_bool(value):
try:
return int(value) in (0, 1)
except (TypeError, ValueError):
return False
def _test_direct_and_complement(context, devices, func):
"""
Test that results are correct and that complement holds.
:param Context context: the libudev context
:param devices: the devices that match
:type devices: frozenset of Device
:param func: the property to test
:type func: device -> bool
"""
assert [device for device in devices if not func(device)] == []
complement = frozenset(context.list_devices()) - devices
assert [device for device in complement if func(device)] == []
def _test_intersection_and_union(context, matches, nomatches):
"""
Test that intersection is empty and union is all of devices.
:param matches: the matching devices
:type matches: frozenset of Device
:param nomatches: the non-matching devices
:type nomatches: frozenset of Device
"""
assert matches & nomatches == frozenset()
assert matches | nomatches == frozenset(context.list_devices())
class TestEnumerator(object):
"""
Test the Enumerator class.
"""
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY)
@settings(max_examples=50)
def test_match_subsystem(self, context, subsystem):
"""
Subsystem match matches devices w/ correct subsystem.
"""
_test_direct_and_complement(
context,
frozenset(context.list_devices().match_subsystem(subsystem)),
lambda d: d.subsystem == subsystem
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY)
@settings(max_examples=5)
def test_match_subsystem_nomatch(self, context, subsystem):
"""
Subsystem no match gets no subsystem with subsystem.
"""
_test_direct_and_complement(
context,
frozenset(
context.list_devices().match_subsystem(subsystem, nomatch=True)
),
lambda d: d.subsystem != subsystem
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY)
@settings(max_examples=5)
def test_match_subsystem_nomatch_unfulfillable(self, context, subsystem):
"""
Combining match and no match should give an empty result.
"""
devices = context.list_devices()
devices.match_subsystem(subsystem)
devices.match_subsystem(subsystem, nomatch=True)
assert not list(devices)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY)
@settings(max_examples=5)
def test_match_subsystem_nomatch_complete(self, context, subsystem):
"""
Test that w/ respect to the universe of devices returned by
list_devices() a match and its inverse are complements of each other.
Note that list_devices() omits devices that have no subsystem,
so with respect to the whole universe of devices, the two are
not complements of each other.
"""
m_devices = frozenset(context.list_devices().match_subsystem(subsystem))
nm_devices = frozenset(
context.list_devices().match_subsystem(subsystem, nomatch=True)
)
_test_intersection_and_union(context, m_devices, nm_devices)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _SYSNAME_STRATEGY)
@settings(max_examples=5)
def test_match_sys_name(self, context, sysname):
"""
A sysname lookup only gives devices with that sysname.
"""
_test_direct_and_complement(
context,
frozenset(context.list_devices().match_sys_name(sysname)),
lambda d: d.sys_name == sysname
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _MATCH_PROPERTY_STRATEGY)
@settings(max_examples=50)
def test_match_property_string(self, context, pair):
"""
Match property only gets devices with that property.
"""
key, value = pair
_test_direct_and_complement(
context,
frozenset(context.list_devices().match_property(key, value)),
lambda d: d.properties.get(key) == value
)
@failed_health_check_wrapper
@given(
_CONTEXT_STRATEGY,
_MATCH_PROPERTY_STRATEGY.filter(lambda x: _is_int(x[1]))
)
@settings(max_examples=50)
def test_match_property_int(self, context, pair):
"""
For a property that might plausibly have an integer value, search
using the integer value and verify that the result all match.
"""
key, value = pair
devices = context.list_devices().match_property(key, int(value))
assert all(
device.properties[key] == value and \
device.properties.asint(key) == int(value) \
for device in devices
)
@failed_health_check_wrapper
@given(
_CONTEXT_STRATEGY,
_MATCH_PROPERTY_STRATEGY.filter(lambda x: _is_bool(x[1]))
)
@settings(max_examples=10)
def test_match_property_bool(self, context, pair):
"""
Verify that a probably boolean property lookup works.
"""
key, value = pair
bool_value = True if int(value) == 1 else False
devices = context.list_devices().match_property(key, bool_value)
assert all(
device.properties[key] == value and \
device.properties.asbool(key) == bool_value \
for device in devices
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _ATTRIBUTE_STRATEGY)
def test_match_attribute_match(self, context, pair):
"""
Test match returns matching devices.
"""
key, value = pair
_test_direct_and_complement(
context,
frozenset(context.list_devices().match_attribute(key, value)),
lambda d: d.attributes.get(key) == value
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _ATTRIBUTE_STRATEGY)
def test_match_attribute_nomatch(self, context, pair):
"""
Test that nomatch returns no devices with attribute value match.
"""
key, value = pair
_test_direct_and_complement(
context,
frozenset(
context.list_devices().match_attribute(key, value, nomatch=True)
),
lambda d: d.attributes.get(key) != value
)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _ATTRIBUTE_STRATEGY)
@settings(max_examples=50)
def test_match_attribute_nomatch_unfulfillable(self, context, pair):
"""
Match and no match for a key/value gives empty set.
"""
key, value = pair
devices = context.list_devices()
devices.match_attribute(key, value)
devices.match_attribute(key, value, nomatch=True)
assert not list(devices)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _ATTRIBUTE_STRATEGY)
@settings(max_examples=50)
def test_match_attribute_nomatch_complete(self, context, pair):
"""
Test that w/ respect to the universe of devices returned by
list_devices() a match and its inverse are complements of each other.
"""
key, value = pair
m_devices = frozenset(
context.list_devices().match_attribute(key, value)
)
nm_devices = frozenset(
context.list_devices().match_attribute(key, value, nomatch=True)
)
_test_intersection_and_union(context, m_devices, nm_devices)
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _ATTRIBUTE_STRATEGY)
@settings(max_examples=50)
def test_match_attribute_string(self, context, pair):
"""
Test that matching attribute as string works.
"""
key, value = pair
devices = context.list_devices().match_attribute(key, value)
assert all(device.attributes.get(key) == value for device in devices)
@failed_health_check_wrapper
@given(
_CONTEXT_STRATEGY,
_ATTRIBUTE_STRATEGY.filter(lambda x: _is_int(x[1]))
)
@settings(max_examples=50)
def test_match_attribute_int(self, context, pair):
"""
Test matching integer attribute.
"""
key, value = pair
int_value = int(value)
devices = context.list_devices().match_attribute(key, int_value)
for device in devices:
attributes = device.attributes
assert attributes.get(key) == value
assert device.attributes.asint(key) == int_value
@failed_health_check_wrapper
@given(
_CONTEXT_STRATEGY,
_ATTRIBUTE_STRATEGY.filter(lambda x: _is_bool(x[1]))
)
@settings(max_examples=50)
def test_match_attribute_bool(self, context, pair):
"""
Test matching boolean attribute.
"""
key, value = pair
bool_value = True if int(value) == 1 else False
devices = context.list_devices().match_attribute(key, bool_value)
for device in devices:
attributes = device.attributes
assert attributes.get(key) == value
assert attributes.asbool(key) == bool_value
@_UDEV_TEST(154, "test_match_tag")
@failed_health_check_wrapper
@given(_CONTEXT_STRATEGY, _TAG_STRATEGY)
@settings(max_examples=50)
def test_match_tag(self, context, tag):
"""
Test that matches returned for tag actually have tag.
"""
_test_direct_and_complement(
context,
frozenset(context.list_devices().match_tag(tag)),
lambda d: tag in d.tags
)
@failed_health_check_wrapper
@given(
_CONTEXT_STRATEGY,
_DEVICE_STRATEGY.filter(lambda d: d.parent is not None)
)
@settings(max_examples=5)
def test_match_parent(self, context, device):
"""
For a given device, verify that it is in its parent's children.
Verify that the parent is also among the device's children,
unless the parent lacks a subsystem
See: rhbz#1255191
"""
parent = device.parent
children = list(context.list_devices().match_parent(parent))
assert device in children
if _UDEV_VERSION <= 175:
assert parent in children
else:
if parent.subsystem is not None:
assert parent in children
else:
assert parent not in children
class TestEnumeratorMatchCombinations(object):
"""
Test combinations of matches.
"""
@given(
_CONTEXT_STRATEGY,
strategies.lists(
elements=_MATCH_PROPERTY_STRATEGY,
min_size=2,
max_size=3,
unique_by=lambda p: p[0]
)
)
@settings(max_examples=20)
def test_combined_property_matches(self, context, ppairs):
"""
Test for behaviour as observed in #1
If matching multiple properties, then the result is the union of
the matching sets, i.e., the resulting filter is a disjunction.
"""
enumeration = context.list_devices()
for key, value in ppairs:
enumeration.match_property(key, value)
_test_direct_and_complement(
context,
frozenset(enumeration),
lambda d: any(
d.properties.get(key) == value for key, value in ppairs
)
)
@given(
_CONTEXT_STRATEGY,
strategies.lists(
elements=_ATTRIBUTE_STRATEGY,
min_size=2,
max_size=3,
unique_by=lambda p: p[0]
)
)
@settings(max_examples=20)
def test_combined_attribute_matches(self, context, apairs):
"""
Test for conjunction of attributes.
If matching multiple attributes, then the result is the intersection of
the matching sets, i.e., the resulting filter is a conjunction.
"""
enumeration = context.list_devices()
for key, value in apairs:
enumeration.match_attribute(key, value)
_test_direct_and_complement(
context,
frozenset(enumeration),
lambda d: all(
d.attributes.get(key) == value for key, value in apairs
)
)
@given(
_CONTEXT_STRATEGY,
strategies.lists(
elements=_MATCH_PROPERTY_STRATEGY,
min_size=1,
max_size=2,
unique_by=lambda p: p[0]
),
strategies.lists(
elements=_ATTRIBUTE_STRATEGY,
min_size=1,
max_size=2,
unique_by=lambda p: p[0]
)
)
@settings(max_examples=20)
def test_combined_matches_of_different_types(self, context, ppairs, apairs):
"""
Require that properties and attributes have a conjunction.
"""
enumeration = context.list_devices()
for key, value in ppairs:
enumeration.match_property(key, value)
for key, value in apairs:
enumeration.match_attribute(key, value)
_test_direct_and_complement(
context,
frozenset(enumeration),
lambda d: all(
d.attributes.get(key) == value for key, value in apairs
) and any(
d.properties.get(key) == value for key, value in ppairs
)
)
@given(
_CONTEXT_STRATEGY,
_SUBSYSTEM_STRATEGY,
_SYSNAME_STRATEGY,
_MATCH_PROPERTY_STRATEGY
)
@settings(max_examples=10)
def test_match(self, context, subsystem, sysname, ppair):
"""
Test that matches from different categories are a conjunction.
"""
prop_name, prop_value = ppair
kwargs = {prop_name: prop_value}
devices = frozenset(
context.list_devices().match(
subsystem=subsystem,
sys_name=sysname,
**kwargs
)
)
_test_direct_and_complement(
context,
devices,
lambda d: d.subsystem == subsystem and d.sys_name == sysname and \
d.properties.get(prop_name) == prop_value
)
class TestEnumeratorMatchMethod(object):
"""
Test the behavior of Enumerator.match.
Only methods that test behavior of this method by patching the Enumerator
object with the methods that match() should invoke belong here.
"""
_ENUMERATOR_STRATEGY = _CONTEXT_STRATEGY.map(lambda x: x.list_devices())
@given(_ENUMERATOR_STRATEGY)
@settings(max_examples=1)
def test_match_passthrough_subsystem(self, enumerator):
"""
Test that special keyword subsystem results in a match_subsystem call.
"""
with mock.patch.object(enumerator, 'match_subsystem',
autospec=True) as match_subsystem:
enumerator.match(subsystem=mock.sentinel.subsystem)
match_subsystem.assert_called_with(mock.sentinel.subsystem)
@given(_ENUMERATOR_STRATEGY)
@settings(max_examples=1)
def test_match_passthrough_sys_name(self, enumerator):
"""
Test that special keyword sys_name results in a match_sys_name call.
"""
with mock.patch.object(enumerator, 'match_sys_name',
autospec=True) as match_sys_name:
enumerator.match(sys_name=mock.sentinel.sys_name)
match_sys_name.assert_called_with(mock.sentinel.sys_name)
@given(_ENUMERATOR_STRATEGY)
@settings(max_examples=1)
def test_match_passthrough_tag(self, enumerator):
"""
Test that special keyword tag results in a match_tag call.
"""
with mock.patch.object(enumerator, 'match_tag',
autospec=True) as match_tag:
enumerator.match(tag=mock.sentinel.tag)
match_tag.assert_called_with(mock.sentinel.tag)
@_UDEV_TEST(172, "test_match_passthrough_parent")
@given(_ENUMERATOR_STRATEGY)
@settings(max_examples=1)
def test_match_passthrough_parent(self, enumerator):
"""
Test that special keyword 'parent' results in a match parent call.
"""
with mock.patch.object(enumerator, 'match_parent',
autospec=True) as match_parent:
enumerator.match(parent=mock.sentinel.parent)
match_parent.assert_called_with(mock.sentinel.parent)
@given(_ENUMERATOR_STRATEGY)
@settings(max_examples=1)
def test_match_passthrough_property(self, enumerator):
"""
Test that non-special keyword args are treated as properties.
"""
with mock.patch.object(enumerator, 'match_property',
autospec=True) as match_property:
enumerator.match(eggs=mock.sentinel.eggs, spam=mock.sentinel.spam)
assert match_property.call_count == 2
posargs = [args for args, _ in match_property.call_args_list]
assert ('spam', mock.sentinel.spam) in posargs
assert ('eggs', mock.sentinel.eggs) in posargs
pyudev-0.21.0/tests/_constants.py 0000664 0001750 0001750 00000007023 12744230565 020157 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.tests._device_tests
==========================
Tests for devices.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from hypothesis import strategies
import pytest
from pyudev import Context
from pyudev import Device
from pyudev import Devices
from pyudev import DeviceNotFoundError
from .utils import udev
_CONTEXT = Context()
def _check_device(device):
"""
Check that device exists by getting it.
"""
try:
Devices.from_path(_CONTEXT, device.sys_path)
return True
except DeviceNotFoundError:
return False
_DEVICE_DATA = udev.DeviceDatabase.db()
_DEVICES = [Devices.from_path(_CONTEXT, d.device_path) for d in _DEVICE_DATA]
_DEVICE_STRATEGY = strategies.sampled_from(_CONTEXT.list_devices())
_DEVICE_STRATEGY = _DEVICE_STRATEGY.filter(_check_device)
_CONTEXT_STRATEGY = strategies.just(_CONTEXT)
_UDEV_VERSION = int(udev.UDevAdm.adm().query_udev_version())
_SUBSYSTEM_STRATEGY = _DEVICE_STRATEGY.map(lambda x: x.subsystem)
# Workaround for issue #181
_SUBSYSTEM_STRATEGY = _SUBSYSTEM_STRATEGY.filter(lambda s: s != 'i2c')
_SYSNAME_STRATEGY = _DEVICE_STRATEGY.map(lambda x: x.sys_name)
_PROPERTY_STRATEGY = _DEVICE_STRATEGY.flatmap(
lambda d: strategies.sampled_from(d.properties.items())
)
_MATCH_PROPERTY_STRATEGY = \
_PROPERTY_STRATEGY.filter(lambda p: p[0][-4:] != "_ENC")
if _UDEV_VERSION <= 230:
_MATCH_PROPERTY_STRATEGY = \
_MATCH_PROPERTY_STRATEGY.filter(lambda p: '[' not in p[1])
# the attributes object for a given device
_ATTRIBUTES_STRATEGY = _DEVICE_STRATEGY.map(lambda d: d.attributes)
# an attribute key and value pair
_ATTRIBUTE_STRATEGY = \
_ATTRIBUTES_STRATEGY.flatmap(
lambda attrs: strategies.sampled_from(attrs.available_attributes).map(
lambda key: (key, attrs.get(key))
)
)
_ATTRIBUTE_STRATEGY = _ATTRIBUTE_STRATEGY.filter(lambda p: p[1] is not None)
if _UDEV_VERSION <= 230:
_ATTRIBUTE_STRATEGY = \
_ATTRIBUTE_STRATEGY.filter(
lambda p: not p[1].startswith(b"\\") and not p[1][-1:] == b" " and \
not p[1].startswith(b'[')
)
# the tags object for a given device
_TAGS_STRATEGY = _DEVICE_STRATEGY.map(lambda d: d.tags)
# an arbitrary tag belonging to a given device
_TAG_STRATEGY = \
_TAGS_STRATEGY.filter(lambda t: sum(1 for _ in t) != 0).flatmap(
strategies.sampled_from
)
def _UDEV_TEST(version, node=None): # pylint: disable=invalid-name
fmt_str = "%s: udev version must be at least %s, is %s"
return pytest.mark.skipif(
_UDEV_VERSION < version,
reason=fmt_str % (node, version, _UDEV_VERSION)
)
pyudev-0.21.0/tests/test_discover.py 0000664 0001750 0001750 00000014405 12744230565 020663 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 Anne Mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
tests.test_discover
===================
Tests discovering what device is meant by somewhat unspecific information.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import pyudev
from pyudev import DeviceFileHypothesis
from pyudev import DeviceNameHypothesis
from pyudev import DeviceNumberHypothesis
from pyudev import DevicePathHypothesis
from pyudev import Discovery
import pytest
from hypothesis import assume
from hypothesis import given
from hypothesis import settings
from hypothesis import strategies
_CONTEXT = pyudev.Context()
_DEVICES = _CONTEXT.list_devices()
NUM_TESTS = 5
class TestUtilities(object):
"""
Some utilities used by the actual tests.
"""
@staticmethod
def get_device_numbers(a_device, a_string):
"""
Get the device number from the device in a few formats.
:param :class:`Device` a_device: the device
:param str a_string: a likely string between the major and minor
:returns: a tuple of device numbers
:rtype: tuple of str
"""
device_number = a_device.device_number
major_number = os.major(device_number)
minor_number = os.minor(device_number)
pair_number = "%s%s%s" % (major_number, a_string, minor_number)
return (str(device_number), pair_number)
@staticmethod
def get_paths(a_device):
"""
Get some variants on a device path.
:param :class:`Device` a_device: the device
:returns: a tuple of paths in sysfs
:rtype: tuple of str
"""
sys_path = a_device.sys_path
(_, _, truncated_path) = sys_path[1:].partition('/')
return (sys_path, truncated_path, a_device.device_path)
@staticmethod
def get_files(a_device):
"""
Get a bunch of files, including device nodes and links.
:param :class:`Device` a_device: the device
:returns: a list of files
:rtype: list of str
"""
links = list(a_device.device_links)
names = [os.path.basename(l) for l in links]
links.extend(names)
device_node = a_device.device_node
if device_node:
links.append(device_node)
return links
class TestDiscovery(object):
"""
Test discovery of an object from limited bits of its description.
"""
_CONTEXT = pyudev.Context()
_DISCOVER = Discovery()
_DISCOVER.setup(_CONTEXT)
@given(
strategies.sampled_from(_DEVICES).filter(lambda x: x.device_number),
strategies.text(":, -/+=").filter(lambda x: x)
)
@settings(max_examples=NUM_TESTS)
def test_device_number(self, a_device, a_string):
"""
Test lookup by a device number.
Note that device number is per class, so there may be two
devices with the same device number.
"""
for number in TestUtilities.get_device_numbers(a_device, a_string):
res = DeviceNumberHypothesis.get_devices(self._CONTEXT, number)
assert a_device in res
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=NUM_TESTS)
def test_path(self, a_device):
"""
Test lookup by path.
"""
for path in TestUtilities.get_paths(a_device):
res = DevicePathHypothesis.get_devices(self._CONTEXT, path)
assert res == set((a_device,))
@given(strategies.sampled_from(_DEVICES))
@settings(max_examples=NUM_TESTS)
def test_name(self, a_device):
"""
Test lookup by device name.
Note that there may be multiple devices corresponding to the name
in different subsystems.
"""
name = a_device.sys_name
res = DeviceNameHypothesis.get_devices(self._CONTEXT, name)
assert a_device in res
_devices = [d for d in _DEVICES if list(d.device_links)]
@pytest.mark.skipif(
len(_devices) == 0,
reason="no device with device links"
)
@given(strategies.sampled_from(_devices))
@settings(max_examples=NUM_TESTS, min_satisfying_examples=1)
def test_device_file(self, a_device):
"""
Test lookup by device file.
Skip any devices that are multipath device paths because links
may point to other paths in multipath group (rhbz#1263441).
"""
assume(not 'DM_MULTIPATH_TIMESTAMP' in a_device.properties)
links = TestUtilities.get_files(a_device)
devs = frozenset(
d for l in links for d in DeviceFileHypothesis.get_devices(
self._CONTEXT,
l
)
)
assert devs == set((a_device,))
@given(
strategies.sampled_from(_DEVICES),
strategies.text(":, -/+=").filter(lambda x: x)
)
@settings(max_examples=NUM_TESTS)
def test_anything(self, a_device, a_string):
"""
Grab any of the likely candidates for looking up a device.
"""
assume(not 'DM_MULTIPATH_TIMESTAMP' in a_device.properties)
values = list(TestUtilities.get_device_numbers(a_device, a_string))
values.extend(TestUtilities.get_paths(a_device))
values.append(a_device.sys_name)
values.extend(TestUtilities.get_files(a_device))
results = frozenset(
d for v in values for d in self._DISCOVER.get_devices(
self._CONTEXT,
v
)
)
assert a_device in results
pyudev-0.21.0/tests/plugins/ 0000775 0001750 0001750 00000000000 12744231232 017101 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/tests/plugins/__init__.py 0000664 0001750 0001750 00000002077 12654153421 021223 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.tests.plugins
====================
Plugins to support the pyudev testsuite.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
pyudev-0.21.0/tests/plugins/travis.py 0000664 0001750 0001750 00000002652 12654153421 020773 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
plugins.travis
==============
Support for Travis CI.
"""
import os
import pytest
def is_on_travis_ci():
"""Determine whether the tests run on Travis CI.
Return ``True``, if so, or ``False`` otherwise.
"""
return os.environ.get('TRAVIS', '') == 'true'
EXPOSED_FUNCTIONS = [is_on_travis_ci]
def pytest_namespace():
return dict((f.__name__, f) for f in EXPOSED_FUNCTIONS)
def pytest_runtest_setup(item):
if not hasattr(item, 'obj'):
return
marker = getattr(item.obj, 'not_on_travis', None)
if marker and is_on_travis_ci():
pytest.skip('Test must not run on Travis CI')
pyudev-0.21.0/tests/plugins/privileged.py 0000664 0001750 0001750 00000004260 12654153421 021612 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
plugins.privileged
==================
Support privileged operations to trigger real udev events.
This plugin adds :func:`load_dummy` and :func:`unload_dummy` to the
:mod:`pytest` namespace.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from subprocess import call
import pytest
def pytest_addoption(parser):
group = parser.getgroup('privileged', 'tests with privileged operations')
group.addoption('--enable-privileged', action='store_true',
help='Enable tests that required privileged operations',
default=False)
def check_privileges_or_skip():
if not pytest.config.option.enable_privileged:
pytest.skip('privileged tests disabled')
def load_dummy():
"""
Load the ``dummy`` module.
If privileged tests are disabled, the current test is skipped.
"""
check_privileges_or_skip()
call(['sudo', 'modprobe', 'dummy'])
def unload_dummy():
"""
Unload the ``dummy`` module.
If privileged tests are disabled, the current test is skipped.
"""
check_privileges_or_skip()
call(['sudo', 'modprobe', '-r', 'dummy'])
EXPOSED_FUNCTIONS = [load_dummy, unload_dummy]
def pytest_namespace():
return dict((f.__name__, f) for f in EXPOSED_FUNCTIONS)
pyudev-0.21.0/tests/plugins/mock_libudev.py 0000664 0001750 0001750 00000006463 12654153421 022132 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""plugins.mock_libudev
====================
Plugin to mock calls to libudev.
This plugin adds :func:`libudev_list()` to the :mod:`pytest` namespace.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from operator import attrgetter
from contextlib import contextmanager
from collections import namedtuple
import mock
Node = namedtuple('Node', 'name value next')
class LinkedList(object):
"""
Linked list class to mock libudev list functions.
"""
@classmethod
def from_iterable(cls, iterable):
"""
Create a list from the given ``iterable``.
"""
next_node = None
for item in reversed(iterable):
if isinstance(item, tuple):
name, value = item
else:
name, value = item, None
node = Node(name, value, next_node)
next_node = node
return cls(next_node)
def __init__(self, first):
self.first = first
@contextmanager
def libudev_list(libudev, function, items):
"""
Mock a libudev linked list::
with pytest.libudev_list(device._libudev, 'udev_device_get_tag_list_entry', ['foo', 'bar']):
assert list(device.tags) == ['foo', 'bar']
``function`` is a string containing the name of the libudev function that
returns the list. ``items`` is an iterable yielding items which shall be
returned by the mocked list function. An item in ``items`` can either be a
tuple with two components, where the first component is the item name, and
the second the item value, or a single element, which is the item name.
The item value is ``None`` in this case.
"""
functions_to_patch = [function, 'udev_list_entry_get_next',
'udev_list_entry_get_name',
'udev_list_entry_get_value']
mocks = dict((f, mock.DEFAULT) for f in functions_to_patch)
with mock.patch.multiple(libudev, **mocks):
udev_list = LinkedList.from_iterable(items)
getattr(libudev, function).return_value = udev_list.first
libudev.udev_list_entry_get_name.side_effect = attrgetter('name')
libudev.udev_list_entry_get_value.side_effect = attrgetter('value')
libudev.udev_list_entry_get_next.side_effect = attrgetter('next')
yield
EXPOSED_FUNCTIONS = [libudev_list]
def pytest_namespace():
return dict((f.__name__, f) for f in EXPOSED_FUNCTIONS)
pyudev-0.21.0/tests/plugins/fake_monitor.py 0000664 0001750 0001750 00000005653 12710747516 022153 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
plugins.fake_monitor
====================
Provide a fake :class:`~pyudev.Monitor`.
This fake monitor allows to trigger arbitrary events. Use this class to
test class building upon monitor without the need to rely on real events
generated by privileged operations as provided by the
:mod:`~plugins.privileged` plugin.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os
from select import select
import pytest
class FakeMonitor(object):
"""
A fake :class:`~pyudev.Monitor` which allows you to trigger arbitrary
events.
This fake monitor implements the complete :class:`~pyudev.Monitor`
interface and works on real file descriptors so that you can
:func:`~select.select()` the monitor.
"""
def __init__(self, device_to_emit):
self._event_source, self._event_sink = os.pipe()
self.device_to_emit = device_to_emit
self.started = False
def trigger_event(self):
"""
Trigger an event on clients of this monitor.
"""
os.write(self._event_sink, b'\x01')
def fileno(self):
return self._event_source
def filter_by(self, *args):
pass
def start(self):
self.started = True
def poll(self, timeout=None):
rlist, _, _ = select([self._event_source], [], [], timeout)
if self._event_source in rlist:
os.read(self._event_source, 1)
return self.device_to_emit
def close(self):
"""
Close sockets acquired by this monitor.
"""
try:
os.close(self._event_source)
finally:
os.close(self._event_sink)
@pytest.fixture
def fake_monitor(request):
"""
Return a FakeMonitor, which emits the platform device as returned by
the ``fake_monitor_device`` funcarg on all triggered actions.
.. warning::
To use this funcarg, you have to provide the ``fake_monitor_device``
funcarg!
"""
return FakeMonitor(request.getfuncargvalue('fake_monitor_device'))
pyudev-0.21.0/PKG-INFO 0000664 0001750 0001750 00000012025 12744231232 015353 0 ustar mulhern mulhern 0000000 0000000 Metadata-Version: 1.1
Name: pyudev
Version: 0.21.0
Summary: A libudev binding
Home-page: http://pyudev.readthedocs.org/
Author: Sebastian Wiesner
Author-email: lunaryorn@gmail.com
License: LGPL 2.1+
Description: ######
pyudev
######
.. image:: https://secure.travis-ci.org/pyudev/pyudev.png?branch=develop
:target: http://travis-ci.org/pyudev/pyudev
http://pyudev.readthedocs.org
pyudev is a LGPL_ licensed, pure Python_ binding for libudev_, the device and
hardware management and information library for Linux. It supports almost all
libudev_ functionality. You can enumerate devices, query device properties and
attributes or monitor devices, including asynchronous monitoring with threads,
or within the event loops of Qt, Glib or wxPython.
The binding supports CPython_ 2 (2.6 or newer) and 3 (3.1 or newer), and PyPy_
1.5 or newer. It is tested against udev 151 or newer, earlier versions of udev
as found on dated Linux systems may work, but are not officially supported.
Usage
-----
Usage of pyudev is quite simply thanks to the power of the underlying udev
library. Getting the labels of all partitions just takes a few lines:
>>> import pyudev
>>> context = pyudev.Context()
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print(device.get('ID_FS_LABEL', 'unlabeled partition'))
...
boot
swap
system
The website_ provides a detailed `user guide`_ and a complete `API reference`_.
Support
-------
Please report issues and questions to the issue tracker, but respect the
following guidelines:
- Check that the issue has not already been reported.
- Check that the issue is not already fixed in the ``master`` branch.
- Open issues with clear title and a detailed description in grammatically
correct, complete sentences.
- Include the Python version and the udev version (see ``udevadm --version``) in
the description of your issue.
Development
-----------
The source code is hosted on GitHub_::
git clone git://github.com/pyudev/pyudev.git
Please fork the repository and send pull requests with your fixes or new
features, but respect the following guidelines:
- Read `how to properly contribute to open source projects on GitHub
`_.
- Understand the `branching model
`_.
- Use a topic branch based on the ``develop`` branch to easily amend a pull
request later, if necessary.
- Write `good commit messages
`_.
- Squash commits on the topic branch before opening a pull request.
- Respect :pep:`8` (use pep8_ to check your coding style compliance).
- Add unit tests if possible (refer to the `testsuite documentation
`_).
- Add API documentation in docstrings.
- Open a `pull request `_
that relates to but one subject with a clear title and description in
grammatically correct, complete sentences.
.. _LGPL: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
.. _Python: http://www.python.org/
.. _CPython: http://www.python.org/
.. _PyPy: http://www.pypy.org/
.. _libudev: http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
.. _website: http://pyudev.readthedocs.org
.. _user guide: http://pyudev.readthedocs.org/en/latest/guide.html
.. _api reference: http://pyudev.readthedocs.org/en/latest/api/index.html
.. _issue tracker: http://github.com/lunaryorn/pyudev/issues
.. _GitHub: http://github.com/lunaryorn/pyudev
.. _git: http://www.git-scm.com/
.. _pep8: http://pypi.python.org/pypi/pep8/
Platform: Linux
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Operating System Kernels :: Linux
pyudev-0.21.0/setup.cfg 0000664 0001750 0001750 00000000265 12744231232 016102 0 ustar mulhern mulhern 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[aliases]
release = egg_info -RDb ''
[upload_docs]
upload_dir = build/sphinx/html
[pytest]
norecursedirs = .* _* build
pyudev-0.21.0/src/ 0000775 0001750 0001750 00000000000 12744231232 015045 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev/ 0000775 0001750 0001750 00000000000 12744231232 016361 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev/core.py 0000664 0001750 0001750 00000032163 12744230565 017700 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.core
===========
Core types and functions of :mod:`pyudev`.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from pyudev.device import Devices
from pyudev.device._errors import DeviceNotFoundAtPathError
from pyudev._ctypeslib.libudev import ERROR_CHECKERS
from pyudev._ctypeslib.libudev import SIGNATURES
from pyudev._ctypeslib.utils import load_ctypes_library
from pyudev._util import ensure_byte_string
from pyudev._util import ensure_unicode_string
from pyudev._util import property_value_to_bytes
from pyudev._util import udev_list_iterate
class Context(object):
"""
A device database connection.
This class represents a connection to the udev device database, and is
really *the* central object to access udev. You need an instance of this
class for almost anything else in pyudev.
This class itself gives access to various udev configuration data (e.g.
:attr:`sys_path`, :attr:`device_path`), and provides device enumeration
(:meth:`list_devices()`).
Instances of this class can directly be given as ``udev *`` to functions
wrapped through :mod:`ctypes`.
"""
def __init__(self):
"""
Create a new context.
"""
self._libudev = load_ctypes_library('udev', SIGNATURES, ERROR_CHECKERS)
self._as_parameter_ = self._libudev.udev_new()
def __del__(self):
self._libudev.udev_unref(self)
@property
def sys_path(self):
"""
The ``sysfs`` mount point defaulting to ``/sys'`` as unicode string.
"""
if hasattr(self._libudev, 'udev_get_sys_path'):
return ensure_unicode_string(self._libudev.udev_get_sys_path(self))
else:
# Fixed path since udev 183
return '/sys'
@property
def device_path(self):
"""
The device directory path defaulting to ``/dev`` as unicode string.
"""
if hasattr(self._libudev, 'udev_get_dev_path'):
return ensure_unicode_string(self._libudev.udev_get_dev_path(self))
else:
# Fixed path since udev 183
return '/dev'
@property
def run_path(self):
"""
The run runtime directory path defaulting to ``/run`` as unicode
string.
.. udevversion:: 167
.. versionadded:: 0.10
"""
if hasattr(self._libudev, 'udev_get_run_path'):
return ensure_unicode_string(self._libudev.udev_get_run_path(self))
else:
return '/run/udev'
@property
def log_priority(self):
"""
The logging priority of the interal logging facitility of udev as
integer with a standard :mod:`syslog` priority. Assign to this
property to change the logging priority.
UDev uses the standard :mod:`syslog` priorities. Constants for these
priorities are defined in the :mod:`syslog` module in the standard
library:
>>> import syslog
>>> context = pyudev.Context()
>>> context.log_priority = syslog.LOG_DEBUG
.. versionadded:: 0.9
"""
return self._libudev.udev_get_log_priority(self)
@log_priority.setter
def log_priority(self, value):
"""
Set the log priority.
:param int value: the log priority.
"""
self._libudev.udev_set_log_priority(self, value)
def list_devices(self, **kwargs):
"""
List all available devices.
The arguments of this method are the same as for
:meth:`Enumerator.match()`. In fact, the arguments are simply passed
straight to method :meth:`~Enumerator.match()`.
This function creates and returns an :class:`Enumerator` object,
that can be used to filter the list of devices, and eventually
retrieve :class:`Device` objects representing matching devices.
.. versionchanged:: 0.8
Accept keyword arguments now for easy matching.
"""
return Enumerator(self).match(**kwargs)
class Enumerator(object):
"""
A filtered iterable of devices.
To retrieve devices, simply iterate over an instance of this class.
This operation yields :class:`Device` objects representing the available
devices.
Before iteration the device list can be filtered by subsystem or by
property values using :meth:`match_subsystem` and
:meth:`match_property`. Multiple subsystem (property) filters are
combined using a logical OR, filters of different types are combined
using a logical AND. The following filter for instance::
devices.match_subsystem('block').match_property(
'ID_TYPE', 'disk').match_property('DEVTYPE', 'disk')
means the following::
subsystem == 'block' and (ID_TYPE == 'disk' or DEVTYPE == 'disk')
Once added, a filter cannot be removed anymore. Create a new object
instead.
Instances of this class can directly be given as given ``udev_enumerate *``
to functions wrapped through :mod:`ctypes`.
"""
def __init__(self, context):
"""
Create a new enumerator with the given ``context`` (a
:class:`Context` instance).
While you can create objects of this class directly, this is not
recommended. Call :method:`Context.list_devices()` instead.
"""
if not isinstance(context, Context):
raise TypeError('Invalid context object')
self.context = context
self._as_parameter_ = context._libudev.udev_enumerate_new(context)
self._libudev = context._libudev
def __del__(self):
self._libudev.udev_enumerate_unref(self)
def match(self, **kwargs):
"""
Include devices according to the rules defined by the keyword
arguments. These keyword arguments are interpreted as follows:
- The value for the keyword argument ``subsystem`` is forwarded to
:meth:`match_subsystem()`.
- The value for the keyword argument ``sys_name`` is forwared to
:meth:`match_sys_name()`.
- The value for the keyword argument ``tag`` is forwared to
:meth:`match_tag()`.
- The value for the keyword argument ``parent`` is forwared to
:meth:`match_parent()`.
- All other keyword arguments are forwareded one by one to
:meth:`match_property()`. The keyword argument itself is interpreted
as property name, the value of the keyword argument as the property
value.
All keyword arguments are optional, calling this method without no
arguments at all is simply a noop.
Return the instance again.
.. versionadded:: 0.8
.. versionchanged:: 0.13
Add ``parent`` keyword.
"""
subsystem = kwargs.pop('subsystem', None)
if subsystem is not None:
self.match_subsystem(subsystem)
sys_name = kwargs.pop('sys_name', None)
if sys_name is not None:
self.match_sys_name(sys_name)
tag = kwargs.pop('tag', None)
if tag is not None:
self.match_tag(tag)
parent = kwargs.pop('parent', None)
if parent is not None:
self.match_parent(parent)
for prop, value in kwargs.items():
self.match_property(prop, value)
return self
def match_subsystem(self, subsystem, nomatch=False):
"""
Include all devices, which are part of the given ``subsystem``.
``subsystem`` is either a unicode string or a byte string, containing
the name of the subsystem. If ``nomatch`` is ``True`` (default is
``False``), the match is inverted: A device is only included if it is
*not* part of the given ``subsystem``.
Note that, if a device has no subsystem, it is not included either
with value of ``nomatch`` True or with value of ``nomatch`` False.
Return the instance again.
"""
match = self._libudev.udev_enumerate_add_nomatch_subsystem \
if nomatch else self._libudev.udev_enumerate_add_match_subsystem
match(self, ensure_byte_string(subsystem))
return self
def match_sys_name(self, sys_name):
"""
Include all devices with the given name.
``sys_name`` is a byte or unicode string containing the device name.
Return the instance again.
.. versionadded:: 0.8
"""
self._libudev.udev_enumerate_add_match_sysname(
self, ensure_byte_string(sys_name))
return self
def match_property(self, prop, value):
"""
Include all devices, whose ``prop`` has the given ``value``.
``prop`` is either a unicode string or a byte string, containing
the name of the property to match. ``value`` is a property value,
being one of the following types:
- :func:`int`
- :func:`bool`
- A byte string
- Anything convertable to a unicode string (including a unicode string
itself)
Return the instance again.
"""
self._libudev.udev_enumerate_add_match_property(
self, ensure_byte_string(prop), property_value_to_bytes(value))
return self
def match_attribute(self, attribute, value, nomatch=False):
"""
Include all devices, whose ``attribute`` has the given ``value``.
``attribute`` is either a unicode string or a byte string, containing
the name of a sys attribute to match. ``value`` is an attribute value,
being one of the following types:
- :func:`int`,
- :func:`bool`
- A byte string
- Anything convertable to a unicode string (including a unicode string
itself)
If ``nomatch`` is ``True`` (default is ``False``), the match is
inverted: A device is include if the ``attribute`` does *not* match
the given ``value``.
.. note::
If ``nomatch`` is ``True``, devices which do not have the given
``attribute`` at all are also included. In other words, with
``nomatch=True`` the given ``attribute`` is *not* guaranteed to
exist on all returned devices.
Return the instance again.
"""
match = (self._libudev.udev_enumerate_add_match_sysattr
if not nomatch else
self._libudev.udev_enumerate_add_nomatch_sysattr)
match(self, ensure_byte_string(attribute),
property_value_to_bytes(value))
return self
def match_tag(self, tag):
"""
Include all devices, which have the given ``tag`` attached.
``tag`` is a byte or unicode string containing the tag name.
Return the instance again.
.. udevversion:: 154
.. versionadded:: 0.6
"""
self._libudev.udev_enumerate_add_match_tag(self, ensure_byte_string(tag))
return self
def match_is_initialized(self):
"""
Include only devices, which are initialized.
Initialized devices have properly set device node permissions and
context, and are (in case of network devices) fully renamed.
Currently this will not affect devices which do not have device nodes
and are not network interfaces.
Return the instance again.
.. seealso:: :attr:`Device.is_initialized`
.. udevversion:: 165
.. versionadded:: 0.8
"""
self._libudev.udev_enumerate_add_match_is_initialized(self)
return self
def match_parent(self, parent):
"""
Include all devices on the subtree of the given ``parent`` device.
The ``parent`` device itself is also included.
``parent`` is a :class:`~pyudev.Device`.
Return the instance again.
.. udevversion:: 172
.. versionadded:: 0.13
"""
self._libudev.udev_enumerate_add_match_parent(self, parent)
return self
def __iter__(self):
"""
Iterate over all matching devices.
Yield :class:`Device` objects.
"""
self._libudev.udev_enumerate_scan_devices(self)
entry = self._libudev.udev_enumerate_get_list_entry(self)
for name, _ in udev_list_iterate(self._libudev, entry):
try:
yield Devices.from_sys_path(self.context, name)
except DeviceNotFoundAtPathError:
continue
pyudev-0.21.0/src/pyudev/pyqt5.py 0000664 0001750 0001750 00000003022 12660444030 020011 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.pyqt5
============
PyQt5 integration.
:class:`MonitorObserver` integrates device monitoring into the PyQt5_
mainloop by turning device events into Qt signals.
:mod:`PyQt5.QtCore` from PyQt5_ must be available when importing this
module.
.. _gPyQt5: http://riverbankcomputing.co.uk/software/pyqt/intro
.. moduleauthor:: Tobias Gehring
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PyQt5 import QtCore
from ._qt_base import MonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)
pyudev-0.21.0/src/pyudev/glib.py 0000664 0001750 0001750 00000013744 12744230565 017671 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""pyudev.glib
===========
Glib integration.
:class:`MonitorObserver` integrates device monitoring into the Glib
mainloop by turing device events into Glib signals.
:mod:`glib` and :mod:`gobject` from PyGObject_ must be available when
importing this module. PyGtk is not required.
.. _PyGObject: http://www.pygtk.org/
.. moduleauthor:: Sebastian Wiesner
.. versionadded:: 0.7
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
# thanks to absolute imports, this really imports the glib binding and not this
# module again
import glib
import gobject
class _ObserverMixin(object):
"""Mixin to provide observer behavior to the old and the new API."""
# pylint: disable=too-few-public-methods
def _setup_observer(self, monitor):
# pylint: disable=attribute-defined-outside-init
self.monitor = monitor
self.event_source = None
self.enabled = True
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events.
.. versionadded:: 0.14
"""
return self.event_source is not None
@enabled.setter
def enabled(self, value):
if value and self.event_source is None:
# pylint: disable=attribute-defined-outside-init
self.event_source = glib.io_add_watch(
self.monitor, glib.IO_IN, self._process_udev_event)
elif not value and self.event_source is not None:
glib.source_remove(self.event_source)
def _process_udev_event(self, source, condition):
# pylint: disable=unused-argument
if condition == glib.IO_IN:
device = self.monitor.poll(timeout=0)
if device is not None:
self._emit_event(device)
return True
def _emit_event(self, device):
self.emit('device-event', device)
class MonitorObserver(gobject.GObject, _ObserverMixin):
"""
An observer for device events integrating into the :mod:`glib` mainloop.
This class inherits :class:`~gobject.GObject` to turn device events into
glib signals.
>>> from pyudev import Context, Monitor
>>> from pyudev.glib import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(observer, device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.connect('device-event', device_event)
>>> monitor.start()
This class is a child of :class:`gobject.GObject`.
"""
__gsignals__ = {
# explicitly convert the signal to str, because glib expects the
# *native* string type of the corresponding python version as type of
# signal name, and str() is the name of the native string type of both
# python versions. We could also remove the "unicode_literals" import,
# but I don't want to make exceptions to the standard set of future
# imports used throughout pyudev for the sake of consistency.
str('device-event'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
}
def __init__(self, monitor):
gobject.GObject.__init__(self)
self._setup_observer(monitor)
gobject.type_register(MonitorObserver)
class GUDevMonitorObserver(gobject.GObject, _ObserverMixin):
"""
An observer for device events integrating into the :mod:`glib` mainloop.
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
_action_signal_map = {
'add': 'device-added', 'remove': 'device-removed',
'change': 'device-changed', 'move': 'device-moved'}
__gsignals__ = {
str('device-event'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)),
str('device-added'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-removed'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-changed'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
str('device-moved'): (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
}
def __init__(self, monitor):
gobject.GObject.__init__(self)
self._setup_observer(monitor)
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.glib.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
self.emit('device-event', device.action, device)
signal = self._action_signal_map.get(device.action)
if signal is not None:
self.emit(signal, device)
gobject.type_register(GUDevMonitorObserver)
pyudev-0.21.0/src/pyudev/version.py 0000664 0001750 0001750 00000002115 12744230565 020427 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.version
==============
Version information.
.. moduleauthor:: mulhern
"""
__version_info__ = (0, 21, 0, '')
__version__ = "%s%s" % \
(
".".join(str(x) for x in __version_info__[:3]),
"".join(str(x) for x in __version_info__[3:])
)
pyudev-0.21.0/src/pyudev/pyqt4.py 0000664 0001750 0001750 00000003672 12660444030 020023 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""
pyudev.pyqt4
============
PyQt4 integration.
:class:`MonitorObserver` integrates device monitoring into the PyQt4\_
mainloop by turning device events into Qt signals.
:mod:`PyQt4.QtCore` from PyQt4\_ must be available when importing this
module.
.. _PyQt4: http://riverbankcomputing.co.uk/software/pyqt/intro
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PyQt4 import QtCore
from ._qt_base import MonitorObserverGenerator
from ._qt_base import QUDevMonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)
"""
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
QUDevMonitorObserver = QUDevMonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.pyqtSignal,
QtCore.QSocketNotifier
)
pyudev-0.21.0/src/pyudev/__init__.py 0000664 0001750 0001750 00000004676 12710747516 020521 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev
======
A binding to libudev.
The :class:`Context` provides the connection to the udev device database
and enumerates devices. Individual devices are represented by the
:class:`Device` class.
Device monitoring is provided by :class:`Monitor` and
:class:`MonitorObserver`. With :mod:`pyudev.pyqt4`, :mod:`pyudev.pyside`,
:mod:`pyudev.glib` and :mod:`pyudev.wx` device monitoring can be integrated
into the event loop of various GUI toolkits.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from pyudev.device import Attributes
from pyudev.device import Device
from pyudev.device import Devices
from pyudev.device import DeviceNotFoundAtPathError
from pyudev.device import DeviceNotFoundByFileError
from pyudev.device import DeviceNotFoundByNameError
from pyudev.device import DeviceNotFoundByNumberError
from pyudev.device import DeviceNotFoundError
from pyudev.device import DeviceNotFoundInEnvironmentError
from pyudev.device import Tags
from pyudev.discover import DeviceFileHypothesis
from pyudev.discover import DeviceNameHypothesis
from pyudev.discover import DeviceNumberHypothesis
from pyudev.discover import DevicePathHypothesis
from pyudev.discover import Discovery
from pyudev.core import Context
from pyudev.core import Enumerator
from pyudev.monitor import Monitor
from pyudev.monitor import MonitorObserver
from pyudev.version import __version__
from pyudev.version import __version_info__
from pyudev._util import udev_version
pyudev-0.21.0/src/pyudev/_qt_base.py 0000664 0001750 0001750 00000015553 12744230565 020531 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._qt_base
===============
Base mixin class for Qt4,Qt5 support.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
from pyudev.device import Device
class MonitorObserverMixin(object):
"""
Base mixin for pyqt monitor observers.
"""
# pylint: disable=too-few-public-methods
def _setup_notifier(self, monitor, notifier_class):
self.monitor = monitor
self.notifier = notifier_class(
monitor.fileno(), notifier_class.Read, self)
self.notifier.activated[int].connect(self._process_udev_event)
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events. This merely
reflects the state of the ``enabled`` property of the underlying
:attr:`notifier`.
.. versionadded:: 0.14
"""
return self.notifier.isEnabled()
@enabled.setter
def enabled(self, value):
self.notifier.setEnabled(value)
def _process_udev_event(self):
"""
Attempt to receive a single device event from the monitor, process
the event and emit corresponding signals.
Called by ``QSocketNotifier``, if data is available on the udev
monitoring socket.
"""
device = self.monitor.poll(timeout=0)
if device is not None:
self._emit_event(device)
def _emit_event(self, device):
self.deviceEvent.emit(device)
class QUDevMonitorObserverMixin(MonitorObserverMixin):
"""
Obsolete monitor observer mixin.
"""
# pylint: disable=too-few-public-methods
def _setup_notifier(self, monitor, notifier_class):
MonitorObserverMixin._setup_notifier(self, monitor, notifier_class)
self._action_signal_map = {
'add': self.deviceAdded, 'remove': self.deviceRemoved,
'change': self.deviceChanged, 'move': self.deviceMoved,
}
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.pyqt4.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
self.deviceEvent.emit(device.action, device)
signal = self._action_signal_map.get(device.action)
if signal is not None:
signal.emit(device)
def make_init(qobject, socket_notifier):
"""
Generates an initializer to observer the given ``monitor``
(a :class:`~pyudev.Monitor`):
``parent`` is the parent :class:`~PyQt{4,5}.QtCore.QObject` of this
object. It is passed unchanged to the inherited constructor of
:class:`~PyQt{4,5}.QtCore.QObject`.
"""
def __init__(self, monitor, parent=None):
qobject.__init__(self, parent)
# pylint: disable=protected-access
self._setup_notifier(monitor, socket_notifier)
return __init__
class MonitorObserverGenerator(object):
"""
Class to generate a MonitorObserver class.
"""
# pylint: disable=too-few-public-methods
@staticmethod
def make_monitor_observer(qobject, signal, socket_notifier):
"""Generates an observer for device events integrating into the
PyQt{4,5} mainloop.
This class inherits :class:`~PyQt{4,5}.QtCore.QObject` to turn device
events into Qt signals:
>>> from pyudev import Context, Monitor
>>> from pyudev.pyqt4 import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.deviceEvent.connect(device_event)
>>> monitor.start()
This class is a child of :class:`~{PySide, PyQt{4,5}}.QtCore.QObject`.
"""
return type(
str("MonitorObserver"),
(qobject, MonitorObserverMixin),
{
str("__init__") : make_init(qobject, socket_notifier),
str("deviceEvent") : signal(Device)
}
)
class QUDevMonitorObserverGenerator(object):
"""
Class to generate a MonitorObserver class.
"""
# pylint: disable=too-few-public-methods
@staticmethod
def make_monitor_observer(qobject, signal, socket_notifier):
"""Generates an observer for device events integrating into the
PyQt{4,5} mainloop.
This class inherits :class:`~PyQt{4,5}.QtCore.QObject` to turn device
events into Qt signals:
>>> from pyudev import Context, Monitor
>>> from pyudev.pyqt4 import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(device):
... print('event {0} on device {1}'.format(device.action, device))
>>> observer.deviceEvent.connect(device_event)
>>> monitor.start()
This class is a child of :class:`~{PyQt{4,5}, PySide}.QtCore.QObject`.
"""
return type(
str("QUDevMonitorObserver"),
(qobject, QUDevMonitorObserverMixin),
{
str("__init__") : make_init(qobject, socket_notifier),
#: emitted upon arbitrary device events
str("deviceEvent") : signal(six.text_type, Device),
#: emitted if a device was added
str("deviceAdded") : signal(Device),
#: emitted if a device was removed
str("deviceRemoved") : signal(Device),
#: emitted if a device was changed
str("deviceChanged") : signal(Device),
#: emitted if a device was moved
str("deviceMoved") : signal(Device)
}
)
pyudev-0.21.0/src/pyudev/_os/ 0000775 0001750 0001750 00000000000 12744231232 017141 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev/_os/__init__.py 0000664 0001750 0001750 00000001741 12660444030 021254 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os
==========
Extras to compensate for deficiencies in python os module.
.. moduleauthor:: mulhern
"""
from . import pipe
from . import poll
pyudev-0.21.0/src/pyudev/_os/pipe.py 0000664 0001750 0001750 00000010703 12744230565 020461 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os.pipe
===============
Fallback implementations for pipe.
1. pipe2 from python os module
2. pipe2 from libc
3. pipe from python os module
The Pipe class wraps the chosen implementation.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import fcntl
from functools import partial
from pyudev._ctypeslib.libc import fd_pair
from pyudev._ctypeslib.libc import ERROR_CHECKERS
from pyudev._ctypeslib.libc import SIGNATURES
from pyudev._ctypeslib.utils import load_ctypes_library
# Define O_CLOEXEC, if not present in os already
O_CLOEXEC = getattr(os, 'O_CLOEXEC', 0o2000000)
def _pipe2_ctypes(libc, flags):
"""A ``pipe2`` implementation using ``pipe2`` from ctypes.
``libc`` is a :class:`ctypes.CDLL` object for libc. ``flags`` is an
integer providing the flags to ``pipe2``.
Return a pair of file descriptors ``(r, w)``.
"""
fds = fd_pair()
libc.pipe2(fds, flags)
return fds[0], fds[1]
def _pipe2_by_pipe(flags):
"""A ``pipe2`` implementation using :func:`os.pipe`.
``flags`` is an integer providing the flags to ``pipe2``.
.. warning::
This implementation is not atomic!
Return a pair of file descriptors ``(r, w)``.
"""
fds = os.pipe()
if flags & os.O_NONBLOCK != 0:
for fd in fds:
set_fd_status_flag(fd, os.O_NONBLOCK)
if flags & O_CLOEXEC != 0:
for fd in fds:
set_fd_flag(fd, O_CLOEXEC)
return fds
def _get_pipe2_implementation():
"""Find the appropriate implementation for ``pipe2``.
Return a function implementing ``pipe2``."""
if hasattr(os, 'pipe2'):
return os.pipe2 # pylint: disable=no-member
else:
try:
libc = load_ctypes_library("libc", SIGNATURES, ERROR_CHECKERS)
return (partial(_pipe2_ctypes, libc)
if hasattr(libc, 'pipe2') else
_pipe2_by_pipe)
except ImportError:
return _pipe2_by_pipe
_PIPE2 = _get_pipe2_implementation()
def set_fd_flag(fd, flag):
"""Set a flag on a file descriptor.
``fd`` is the file descriptor or file object, ``flag`` the flag as integer.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFD, 0)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | flag)
def set_fd_status_flag(fd, flag):
"""Set a status flag on a file descriptor.
``fd`` is the file descriptor or file object, ``flag`` the flag as integer.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
fcntl.fcntl(fd, fcntl.F_SETFL, flags | flag)
class Pipe(object):
"""A unix pipe.
A pipe object provides two file objects: :attr:`source` is a readable file
object, and :attr:`sink` a writeable. Bytes written to :attr:`sink` appear
at :attr:`source`.
Open a pipe with :meth:`open()`.
"""
@classmethod
def open(cls):
"""Open and return a new :class:`Pipe`.
The pipe uses non-blocking IO."""
source, sink = _PIPE2(os.O_NONBLOCK | O_CLOEXEC)
return cls(source, sink)
def __init__(self, source_fd, sink_fd):
"""Create a new pipe object from the given file descriptors.
``source_fd`` is a file descriptor for the readable side of the pipe,
``sink_fd`` is a file descriptor for the writeable side."""
self.source = os.fdopen(source_fd, 'rb', 0)
self.sink = os.fdopen(sink_fd, 'wb', 0)
def close(self):
"""Closes both sides of the pipe."""
try:
self.source.close()
finally:
self.sink.close()
pyudev-0.21.0/src/pyudev/_os/poll.py 0000664 0001750 0001750 00000010033 12743210422 020453 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._os.poll
===============
Operating system interface for pyudev.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import select
from pyudev._util import eintr_retry_call
class Poll(object):
"""A poll object.
This object essentially provides a more convenient interface around
:class:`select.poll`.
"""
_EVENT_TO_MASK = {'r': select.POLLIN,
'w': select.POLLOUT}
@staticmethod
def _has_event(events, event):
return events & event != 0
@classmethod
def for_events(cls, *events):
"""Listen for ``events``.
``events`` is a list of ``(fd, event)`` pairs, where ``fd`` is a file
descriptor or file object and ``event`` either ``'r'`` or ``'w'``. If
``r``, listen for whether that is ready to be read. If ``w``, listen
for whether the channel is ready to be written to.
"""
notifier = eintr_retry_call(select.poll)
for fd, event in events:
mask = cls._EVENT_TO_MASK.get(event)
if not mask:
raise ValueError('Unknown event type: {0!r}'.format(event))
notifier.register(fd, mask)
return cls(notifier)
def __init__(self, notifier):
"""Create a poll object for the given ``notifier``.
``notifier`` is the :class:`select.poll` object wrapped by the new poll
object.
"""
self._notifier = notifier
def poll(self, timeout=None):
"""Poll for events.
``timeout`` is an integer specifying how long to wait for events (in
milliseconds). If omitted, ``None`` or negative, wait until an event
occurs.
Return a list of all events that occurred before ``timeout``, where
each event is a pair ``(fd, event)``. ``fd`` is the integral file
descriptor, and ``event`` a string indicating the event type. If
``'r'``, there is data to read from ``fd``. If ``'w'``, ``fd`` is
writable without blocking now. If ``'h'``, the file descriptor was
hung up (i.e. the remote side of a pipe was closed).
"""
# Return a list to allow clients to determine whether there are any
# events at all with a simple truthiness test.
return list(self._parse_events(eintr_retry_call(self._notifier.poll, timeout)))
def _parse_events(self, events):
"""Parse ``events``.
``events`` is a list of events as returned by
:meth:`select.poll.poll()`.
Yield all parsed events.
"""
for fd, event_mask in events:
if self._has_event(event_mask, select.POLLNVAL):
raise IOError('File descriptor not open: {0!r}'.format(fd))
elif self._has_event(event_mask, select.POLLERR):
raise IOError('Error while polling fd: {0!r}'.format(fd))
if self._has_event(event_mask, select.POLLIN):
yield fd, 'r'
if self._has_event(event_mask, select.POLLOUT):
yield fd, 'w'
if self._has_event(event_mask, select.POLLHUP):
yield fd, 'h'
pyudev-0.21.0/src/pyudev/_ctypeslib/ 0000775 0001750 0001750 00000000000 12744231232 020516 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev/_ctypeslib/_errorcheckers.py 0000664 0001750 0001750 00000006241 12744230565 024103 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib._errorcheckers
================================
Error checkers for ctypes wrappers.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import errno
from ctypes import get_errno
ERRNO_EXCEPTIONS = {
errno.ENOMEM: MemoryError,
errno.EOVERFLOW: OverflowError,
errno.EINVAL: ValueError
}
def exception_from_errno(errnum):
"""Create an exception from ``errnum``.
``errnum`` is an integral error number.
Return an exception object appropriate to ``errnum``.
"""
exception = ERRNO_EXCEPTIONS.get(errnum)
errorstr = os.strerror(errnum)
if exception is not None:
return exception(errorstr)
else:
return EnvironmentError(errnum, errorstr)
def check_negative_errorcode(result, func, *args):
"""Error checker for funtions, which return negative error codes.
If ``result`` is smaller than ``0``, it is interpreted as negative error
code, and an appropriate exception is raised:
- ``-ENOMEM`` raises a :exc:`~exceptions.MemoryError`
- ``-EOVERFLOW`` raises a :exc:`~exceptions.OverflowError`
- all other error codes raise :exc:`~exceptions.EnvironmentError`
If result is greater or equal to ``0``, it is returned unchanged.
"""
if result < 0:
# udev returns the *negative* errno code at this point
errnum = -result
raise exception_from_errno(errnum)
else:
return result
def check_errno_on_nonzero_return(result, func, *args):
"""Error checker to check the system ``errno`` as returned by
:func:`ctypes.get_errno()`.
If ``result`` is not ``0``, an exception according to this errno is raised.
Otherwise nothing happens.
"""
if result != 0:
errnum = get_errno()
if errnum != 0:
raise exception_from_errno(errnum)
return result
def check_errno_on_null_pointer_return(result, func, *args):
"""Error checker to check the system ``errno`` as returned by
:func:`ctypes.get_errno()`.
If ``result`` is a null pointer, an exception according to this errno is
raised. Otherwise nothing happens.
"""
# pylint: disable=invalid-name
if not result:
errnum = get_errno()
if errnum != 0:
raise exception_from_errno(errnum)
return result
pyudev-0.21.0/src/pyudev/_ctypeslib/__init__.py 0000664 0001750 0001750 00000001717 12660444030 022634 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib
=================
Wrappers for libraries.
.. moduleauthor:: mulhern
"""
from . import libc
from . import libudev
pyudev-0.21.0/src/pyudev/_ctypeslib/utils.py 0000664 0001750 0001750 00000004651 12744230565 022246 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.utils
=======================
Utilities for loading ctypeslib.
.. moduleauthor:: Anne Mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import CDLL
from ctypes.util import find_library
def load_ctypes_library(name, signatures, error_checkers):
"""
Load library ``name`` and return a :class:`ctypes.CDLL` object for it.
:param str name: the library name
:param signatures: signatures of methods
:type signatures: dict of str * (tuple of (list of type) * type)
:param error_checkers: error checkers for methods
:type error_checkers: dict of str * ((int * ptr * arglist) -> int)
The library has errno handling enabled.
Important functions are given proper signatures and return types to support
type checking and argument conversion.
:returns: a loaded library
:rtype: ctypes.CDLL
:raises ImportError: if the library is not found
"""
library_name = find_library(name)
if not library_name:
raise ImportError('No library named %s' % name)
lib = CDLL(library_name, use_errno=True)
# Add function signatures
for funcname, signature in signatures.items():
function = getattr(lib, funcname, None)
if function:
argtypes, restype = signature
function.argtypes = argtypes
function.restype = restype
errorchecker = error_checkers.get(funcname)
if errorchecker:
function.errcheck = errorchecker
return lib
pyudev-0.21.0/src/pyudev/_ctypeslib/libc.py 0000664 0001750 0001750 00000002474 12744230565 022020 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.libc
======================
Wrappers for libc.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import c_int
from ._errorcheckers import check_errno_on_nonzero_return
fd_pair = c_int * 2
SIGNATURES = dict(
pipe2=([fd_pair, c_int], c_int),
)
ERROR_CHECKERS = dict(
pipe2=check_errno_on_nonzero_return,
)
pyudev-0.21.0/src/pyudev/_ctypeslib/libudev.py 0000664 0001750 0001750 00000025053 12744230565 022537 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._ctypeslib.libudev
=========================
Wrapper types for libudev. Use ``libudev`` attribute to access libudev
functions.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from ctypes import c_char
from ctypes import c_char_p
from ctypes import c_int
from ctypes import c_uint
from ctypes import c_ulonglong
from ctypes import CDLL
from ctypes import Structure
from ctypes import POINTER
from ctypes.util import find_library
from ._errorcheckers import check_errno_on_nonzero_return
from ._errorcheckers import check_errno_on_null_pointer_return
from ._errorcheckers import check_negative_errorcode
class udev(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_p = POINTER(udev) # pylint: disable=invalid-name
class udev_enumerate(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_enumerate`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_enumerate_p = POINTER(udev_enumerate) # pylint: disable=invalid-name
class udev_list_entry(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_list_entry`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_list_entry_p = POINTER(udev_list_entry) # pylint: disable=invalid-name
class udev_device(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_device`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_device_p = POINTER(udev_device) # pylint: disable=invalid-name
class udev_monitor(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_device`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_monitor_p = POINTER(udev_monitor) # pylint: disable=invalid-name
class udev_hwdb(Structure): # pylint: disable=invalid-name
"""
Dummy for ``udev_hwdb`` structure.
"""
# pylint: disable=too-few-public-methods
pass
udev_hwdb_p = POINTER(udev_hwdb) # pylint: disable=invalid-name
dev_t = c_ulonglong # pylint: disable=invalid-name
SIGNATURES = dict(
# context
udev_new=([], udev_p),
udev_unref=([udev_p], None),
udev_ref=([udev_p], udev_p),
udev_get_sys_path=([udev_p], c_char_p),
udev_get_dev_path=([udev_p], c_char_p),
udev_get_run_path=([udev_p], c_char_p),
udev_get_log_priority=([udev_p], c_int),
udev_set_log_priority=([udev_p, c_int], None),
udev_enumerate_new=([udev_p], udev_enumerate_p),
udev_enumerate_ref=([udev_enumerate_p], udev_enumerate_p),
udev_enumerate_unref=([udev_enumerate_p], None),
udev_enumerate_add_match_subsystem=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_nomatch_subsystem=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_property=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_match_sysattr=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_nomatch_sysattr=(
[udev_enumerate_p, c_char_p, c_char_p],
c_int
),
udev_enumerate_add_match_tag=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_sysname=([udev_enumerate_p, c_char_p], c_int),
udev_enumerate_add_match_parent=([udev_enumerate_p, udev_device_p], c_int),
udev_enumerate_add_match_is_initialized=([udev_enumerate_p], c_int),
udev_enumerate_scan_devices=([udev_enumerate_p], c_int),
udev_enumerate_get_list_entry=([udev_enumerate_p], udev_list_entry_p),
# list entries
udev_list_entry_get_next=([udev_list_entry_p], udev_list_entry_p),
udev_list_entry_get_name=([udev_list_entry_p], c_char_p),
udev_list_entry_get_value=([udev_list_entry_p], c_char_p),
# devices
udev_device_ref=([udev_device_p], udev_device_p),
udev_device_unref=([udev_device_p], None),
udev_device_new_from_syspath=([udev_p, c_char_p], udev_device_p),
udev_device_new_from_subsystem_sysname=(
[udev_p, c_char_p, c_char_p],
udev_device_p
),
udev_device_new_from_devnum=([udev_p, c_char, dev_t], udev_device_p),
udev_device_new_from_device_id=([udev_p, c_char_p], udev_device_p),
udev_device_new_from_environment=([udev_p], udev_device_p),
udev_device_get_parent=([udev_device_p], udev_device_p),
udev_device_get_parent_with_subsystem_devtype=(
[udev_device_p, c_char_p, c_char_p],
udev_device_p
),
udev_device_get_devpath=([udev_device_p], c_char_p),
udev_device_get_subsystem=([udev_device_p], c_char_p),
udev_device_get_syspath=([udev_device_p], c_char_p),
udev_device_get_sysnum=([udev_device_p], c_char_p),
udev_device_get_sysname=([udev_device_p], c_char_p),
udev_device_get_driver=([udev_device_p], c_char_p),
udev_device_get_devtype=([udev_device_p], c_char_p),
udev_device_get_devnode=([udev_device_p], c_char_p),
udev_device_get_property_value=([udev_device_p, c_char_p], c_char_p),
udev_device_get_sysattr_value=([udev_device_p, c_char_p], c_char_p),
udev_device_get_devnum=([udev_device_p], dev_t),
udev_device_get_action=([udev_device_p], c_char_p),
udev_device_get_seqnum=([udev_device_p], c_ulonglong),
udev_device_get_is_initialized=([udev_device_p], c_int),
udev_device_get_usec_since_initialized=([udev_device_p], c_ulonglong),
udev_device_get_devlinks_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_tags_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_properties_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_get_sysattr_list_entry=([udev_device_p], udev_list_entry_p),
udev_device_set_sysattr_value=([udev_device_p, c_char_p, c_char_p], c_int),
udev_device_has_tag=([udev_device_p, c_char_p], c_int),
# monitoring
udev_monitor_ref=([udev_monitor_p], udev_monitor_p),
udev_monitor_unref=([udev_monitor_p], None),
udev_monitor_new_from_netlink=([udev_p, c_char_p], udev_monitor_p),
udev_monitor_enable_receiving=([udev_monitor_p], c_int),
udev_monitor_set_receive_buffer_size=([udev_monitor_p, c_int], c_int),
udev_monitor_get_fd=([udev_monitor_p], c_int),
udev_monitor_receive_device=([udev_monitor_p], udev_device_p),
udev_monitor_filter_add_match_subsystem_devtype=(
[udev_monitor_p, c_char_p, c_char_p], c_int),
udev_monitor_filter_add_match_tag=([udev_monitor_p, c_char_p], c_int),
udev_monitor_filter_update=([udev_monitor_p], c_int),
udev_monitor_filter_remove=([udev_monitor_p], c_int),
# hwdb
udev_hwdb_ref=([udev_hwdb_p], udev_hwdb_p),
udev_hwdb_unref=([udev_hwdb_p], None),
udev_hwdb_new=([udev_p], udev_hwdb_p),
udev_hwdb_get_properties_list_entry=(
[udev_hwdb_p, c_char_p, c_uint],
udev_list_entry_p
)
)
ERROR_CHECKERS = dict(
udev_device_get_action=None,
udev_device_get_devlinks_list_entry=None,
udev_device_get_devnode=None,
udev_device_get_devnum=None,
udev_device_get_devpath=None,
udev_device_get_devtype=None,
udev_device_get_driver=None,
udev_device_get_is_initialized=None,
udev_device_get_parent=None,
udev_device_get_parent_with_subsystem_devtype=None,
udev_device_get_properties_list_entry=None,
udev_device_get_property_value=None,
udev_device_get_seqnum=None,
udev_device_get_subsystem=None,
udev_device_get_sysattr_list_entry=None,
udev_device_get_sysattr_value=None,
udev_device_get_sysname=None,
udev_device_get_sysnum=None,
udev_device_get_syspath=None,
udev_device_get_tags_list_entry=None,
udev_device_get_usec_since_initialized=None,
udev_device_has_tag=None,
udev_device_new_from_device_id=None,
udev_device_new_from_devnum=None,
udev_device_new_from_environment=None,
udev_device_new_from_subsystem_sysname=None,
udev_device_new_from_syspath=None,
udev_device_ref=None,
udev_device_unref=None,
udev_device_set_sysattr_value=check_negative_errorcode,
udev_enumerate_add_match_parent=check_negative_errorcode,
udev_enumerate_add_match_subsystem=check_negative_errorcode,
udev_enumerate_add_nomatch_subsystem=check_negative_errorcode,
udev_enumerate_add_match_property=check_negative_errorcode,
udev_enumerate_add_match_sysattr=check_negative_errorcode,
udev_enumerate_add_nomatch_sysattr=check_negative_errorcode,
udev_enumerate_add_match_tag=check_negative_errorcode,
udev_enumerate_add_match_sysname=check_negative_errorcode,
udev_enumerate_add_match_is_initialized=check_negative_errorcode,
udev_enumerate_get_list_entry=None,
udev_enumerate_new=None,
udev_enumerate_ref=None,
udev_enumerate_scan_devices=None,
udev_enumerate_unref=None,
udev_get_dev_path=None,
udev_get_log_priority=None,
udev_get_run_path=None,
udev_get_sys_path=None,
udev_hwdb_get_properties_list_entry=None,
udev_hwdb_new=None,
udev_hwdb_ref=None,
udev_hwdb_unref=None,
udev_list_entry_get_name=None,
udev_list_entry_get_next=None,
udev_list_entry_get_value=None,
udev_monitor_set_receive_buffer_size=check_errno_on_nonzero_return,
# libudev doc says, enable_receiving returns a negative errno, but tests
# show that this is not reliable, so query the real error code
udev_monitor_enable_receiving=check_errno_on_nonzero_return,
udev_monitor_receive_device=check_errno_on_null_pointer_return,
udev_monitor_ref=None,
udev_monitor_filter_add_match_subsystem_devtype=check_negative_errorcode,
udev_monitor_filter_add_match_tag=check_negative_errorcode,
udev_monitor_filter_update=check_errno_on_nonzero_return,
udev_monitor_filter_remove=check_errno_on_nonzero_return,
udev_monitor_get_fd=None,
udev_monitor_new_from_netlink=None,
udev_monitor_unref=None,
udev_new=None,
udev_ref=None,
udev_set_log_priority=None,
udev_unref=None
)
pyudev-0.21.0/src/pyudev/_util.py 0000664 0001750 0001750 00000015450 12710747516 020066 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._util
============
Internal utilities
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
try:
from subprocess import check_output
except ImportError:
from pyudev._compat import check_output
import os
import sys
import stat
import errno
import six
def ensure_byte_string(value):
"""
Return the given ``value`` as bytestring.
If the given ``value`` is not a byte string, but a real unicode string, it
is encoded with the filesystem encoding (as in
:func:`sys.getfilesystemencoding()`).
"""
if not isinstance(value, bytes):
value = value.encode(sys.getfilesystemencoding())
return value
def ensure_unicode_string(value):
"""
Return the given ``value`` as unicode string.
If the given ``value`` is not a unicode string, but a byte string, it is
decoded with the filesystem encoding (as in
:func:`sys.getfilesystemencoding()`).
"""
if not isinstance(value, six.text_type):
value = value.decode(sys.getfilesystemencoding())
return value
def property_value_to_bytes(value):
"""
Return a byte string, which represents the given ``value`` in a way
suitable as raw value of an udev property.
If ``value`` is a boolean object, it is converted to ``'1'`` or ``'0'``,
depending on whether ``value`` is ``True`` or ``False``. If ``value`` is a
byte string already, it is returned unchanged. Anything else is simply
converted to a unicode string, and then passed to
:func:`ensure_byte_string`.
"""
# udev represents boolean values as 1 or 0, therefore an explicit
# conversion to int is required for boolean values
if isinstance(value, bool):
value = int(value)
if isinstance(value, bytes):
return value
else:
return ensure_byte_string(six.text_type(value))
def string_to_bool(value):
"""
Convert the given unicode string ``value`` to a boolean object.
If ``value`` is ``'1'``, ``True`` is returned. If ``value`` is ``'0'``,
``False`` is returned. Any other value raises a
:exc:`~exceptions.ValueError`.
"""
if value not in ('1', '0'):
raise ValueError('Not a boolean value: {0!r}'.format(value))
return value == '1'
def udev_list_iterate(libudev, entry):
"""
Iteration helper for udev list entry objects.
Yield a tuple ``(name, value)``. ``name`` and ``value`` are bytestrings
containing the name and the value of the list entry. The exact contents
depend on the list iterated over.
"""
while entry:
name = libudev.udev_list_entry_get_name(entry)
value = libudev.udev_list_entry_get_value(entry)
yield (name, value)
entry = libudev.udev_list_entry_get_next(entry)
def get_device_type(filename):
"""
Get the device type of a device file.
``filename`` is a string containing the path of a device file.
Return ``'char'`` if ``filename`` is a character device, or ``'block'`` if
``filename`` is a block device. Raise :exc:`~exceptions.ValueError` if
``filename`` is no device file at all. Raise
:exc:`~exceptions.EnvironmentError` if ``filename`` does not exist or if
its metadata was inaccessible.
.. versionadded:: 0.15
"""
mode = os.stat(filename).st_mode
if stat.S_ISCHR(mode):
return 'char'
elif stat.S_ISBLK(mode):
return 'block'
else:
raise ValueError('not a device file: {0!r}'.format(filename))
def eintr_retry_call(func, *args, **kwargs):
"""
Handle interruptions to an interruptible system call.
Run an interruptible system call in a loop and retry if it raises EINTR.
The signal calls that may raise EINTR prior to Python 3.5 are listed in
PEP 0475. Any calls to these functions must be wrapped in eintr_retry_call
in order to handle EINTR returns in older versions of Python.
This function is safe to use under Python 3.5 and newer since the wrapped
function will simply return without raising EINTR.
This function is based on _eintr_retry_call in python's subprocess.py.
"""
# select.error inherits from Exception instead of OSError in Python 2
import select
while True:
try:
return func(*args, **kwargs)
except (OSError, IOError, select.error) as err:
# If this is not an IOError or OSError, it's the old select.error
# type, which means that the errno is only accessible via subscript
if isinstance(err, (OSError, IOError)):
error_code = err.errno
else:
error_code = err.args[0]
if error_code == errno.EINTR:
continue
raise
def udev_version():
"""
Get the version of the underlying udev library.
udev doesn't use a standard major-minor versioning scheme, but instead
labels releases with a single consecutive number. Consequently, the
version number returned by this function is a single integer, and not a
tuple (like for instance the interpreter version in
:data:`sys.version_info`).
As libudev itself does not provide a function to query the version number,
this function calls the ``udevadm`` utility, so be prepared to catch
:exc:`~exceptions.EnvironmentError` and
:exc:`~subprocess.CalledProcessError` if you call this function.
Return the version number as single integer. Raise
:exc:`~exceptions.ValueError`, if the version number retrieved from udev
could not be converted to an integer. Raise
:exc:`~exceptions.EnvironmentError`, if ``udevadm`` was not found, or could
not be executed. Raise :exc:`subprocess.CalledProcessError`, if
``udevadm`` returned a non-zero exit code. On Python 2.7 or newer, the
``output`` attribute of this exception is correctly set.
.. versionadded:: 0.8
"""
output = ensure_unicode_string(check_output(['udevadm', '--version']))
return int(output.strip())
pyudev-0.21.0/src/pyudev/device/ 0000775 0001750 0001750 00000000000 12744231232 017620 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev/device/_device.py 0000664 0001750 0001750 00000126711 12744230565 021610 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.device._device
=====================
Device class implementation of :mod:`pyudev`.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import re
from collections import Container
from collections import Iterable
from collections import Mapping
from datetime import timedelta
from pyudev.device._errors import DeviceNotFoundAtPathError
from pyudev.device._errors import DeviceNotFoundByFileError
from pyudev.device._errors import DeviceNotFoundByInterfaceIndexError
from pyudev.device._errors import DeviceNotFoundByKernelDeviceError
from pyudev.device._errors import DeviceNotFoundByNameError
from pyudev.device._errors import DeviceNotFoundByNumberError
from pyudev.device._errors import DeviceNotFoundInEnvironmentError
from pyudev._util import ensure_byte_string
from pyudev._util import ensure_unicode_string
from pyudev._util import get_device_type
from pyudev._util import string_to_bool
from pyudev._util import udev_list_iterate
# pylint: disable=too-many-lines
class Devices(object):
"""
Class for constructing :class:`Device` objects from various kinds of data.
"""
@classmethod
def from_path(cls, context, path):
"""
Create a device from a device ``path``. The ``path`` may or may not
start with the ``sysfs`` mount point:
>>> from pyudev import Context, Device
>>> context = Context()
>>> Devices.from_path(context, '/devices/platform')
Device(u'/sys/devices/platform')
>>> Devices.from_path(context, '/sys/devices/platform')
Device(u'/sys/devices/platform')
``context`` is the :class:`Context` in which to search the device.
``path`` is a device path as unicode or byte string.
Return a :class:`Device` object for the device. Raise
:exc:`DeviceNotFoundAtPathError`, if no device was found for ``path``.
.. versionadded:: 0.18
"""
if not path.startswith(context.sys_path):
path = os.path.join(context.sys_path, path.lstrip(os.sep))
return cls.from_sys_path(context, path)
@classmethod
def from_sys_path(cls, context, sys_path):
"""
Create a new device from a given ``sys_path``:
>>> from pyudev import Context, Device
>>> context = Context()
>>> Devices.from_sys_path(context, '/sys/devices/platform')
Device(u'/sys/devices/platform')
``context`` is the :class:`Context` in which to search the device.
``sys_path`` is a unicode or byte string containing the path of the
device inside ``sysfs`` with the mount point included.
Return a :class:`Device` object for the device. Raise
:exc:`DeviceNotFoundAtPathError`, if no device was found for
``sys_path``.
.. versionadded:: 0.18
"""
device = context._libudev.udev_device_new_from_syspath(
context, ensure_byte_string(sys_path))
if not device:
raise DeviceNotFoundAtPathError(sys_path)
return Device(context, device)
@classmethod
def from_name(cls, context, subsystem, sys_name):
"""
Create a new device from a given ``subsystem`` and a given
``sys_name``:
>>> from pyudev import Context, Device
>>> context = Context()
>>> sda = Devices.from_name(context, 'block', 'sda')
>>> sda
Device(u'/sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda')
>>> sda == Devices.from_path(context, '/block/sda')
``context`` is the :class:`Context` in which to search the device.
``subsystem`` and ``sys_name`` are byte or unicode strings, which
denote the subsystem and the name of the device to create.
Return a :class:`Device` object for the device. Raise
:exc:`DeviceNotFoundByNameError`, if no device was found with the given
name.
.. versionadded:: 0.18
"""
sys_name = sys_name.replace("/", "!")
device = context._libudev.udev_device_new_from_subsystem_sysname(
context, ensure_byte_string(subsystem),
ensure_byte_string(sys_name))
if not device:
raise DeviceNotFoundByNameError(subsystem, sys_name)
return Device(context, device)
@classmethod
def from_device_number(cls, context, typ, number):
"""
Create a new device from a device ``number`` with the given device
``type``:
>>> import os
>>> from pyudev import Context, Device
>>> ctx = Context()
>>> major, minor = 8, 0
>>> device = Devices.from_device_number(context, 'block',
... os.makedev(major, minor))
>>> device
Device(u'/sys/devices/pci0000:00/0000:00:11.0/host0/target0:0:0/0:0:0:0/block/sda')
>>> os.major(device.device_number), os.minor(device.device_number)
(8, 0)
Use :func:`os.makedev` to construct a device number from a major and a
minor device number, as shown in the example above.
.. warning::
Device numbers are not unique across different device types.
Passing a correct number with a wrong type may silently yield a
wrong device object, so make sure to pass the correct device type.
``context`` is the :class:`Context`, in which to search the device.
``type`` is either ``'char'`` or ``'block'``, according to whether the
device is a character or block device. ``number`` is the device number
as integer.
Return a :class:`Device` object for the device with the given device
``number``. Raise :exc:`DeviceNotFoundByNumberError`, if no device was
found with the given device type and number.
.. versionadded:: 0.18
"""
device = context._libudev.udev_device_new_from_devnum(
context, ensure_byte_string(typ[0]), number)
if not device:
raise DeviceNotFoundByNumberError(typ, number)
return Device(context, device)
@classmethod
def from_device_file(cls, context, filename):
"""
Create a new device from the given device file:
>>> from pyudev import Context, Device
>>> context = Context()
>>> device = Devices.from_device_file(context, '/dev/sda')
>>> device
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
>>> device.device_node
u'/dev/sda'
.. warning::
Though the example seems to suggest that ``device.device_node ==
filename`` holds with ``device = Devices.from_device_file(context,
filename)``, this is only true in a majority of cases. There *can*
be devices, for which this relation is actually false! Thus, do
*not* expect :attr:`~Device.device_node` to be equal to the given
``filename`` for the returned :class:`Device`. Especially, use
:attr:`~Device.device_node` if you need the device file of a
:class:`Device` created with this method afterwards.
``context`` is the :class:`Context` in which to search the device.
``filename`` is a string containing the path of a device file.
Return a :class:`Device` representing the given device file. Raise
:exc:`DeviceNotFoundByFileError` if ``filename`` is no device file
at all or if ``filename`` does not exist or if its metadata was
inaccessible.
.. versionadded:: 0.18
"""
try:
device_type = get_device_type(filename)
device_number = os.stat(filename).st_rdev
except (EnvironmentError, ValueError) as err:
raise DeviceNotFoundByFileError(err)
return cls.from_device_number(context, device_type, device_number)
@classmethod
def from_interface_index(cls, context, ifindex):
"""
Locate a device based on the interface index.
:param `Context` context: the libudev context
:param int ifindex: the interface index
:returns: the device corresponding to the interface index
:rtype: `Device`
This method is only appropriate for network devices.
"""
network_devices = context.list_devices(subsystem='net')
dev = next(
(d for d in network_devices if \
d.attributes.get('ifindex') == ifindex),
None
)
if dev is not None:
return dev
else:
raise DeviceNotFoundByInterfaceIndexError(ifindex)
@classmethod
def from_kernel_device(cls, context, kernel_device):
"""
Locate a device based on the kernel device.
:param `Context` context: the libudev context
:param str kernel_device: the kernel device
:returns: the device corresponding to ``kernel_device``
:rtype: `Device`
"""
switch_char = kernel_device[0]
rest = kernel_device[1:]
if switch_char in ('b', 'c'):
number_re = re.compile(r'^(?P\d+):(?P\d+)$')
match = number_re.match(rest)
if match:
number = os.makedev(
int(match.group('major')),
int(match.group('minor'))
)
return cls.from_device_number(context, switch_char, number)
else:
raise DeviceNotFoundByKernelDeviceError(kernel_device)
elif switch_char == 'n':
return cls.from_interface_index(context, rest)
elif switch_char == '+':
(subsystem, _, kernel_device_name) = rest.partition(':')
if kernel_device_name and subsystem:
return cls.from_name(context, subsystem, kernel_device_name)
else:
raise DeviceNotFoundByKernelDeviceError(kernel_device)
else:
raise DeviceNotFoundByKernelDeviceError(kernel_device)
@classmethod
def from_environment(cls, context):
"""
Create a new device from the process environment (as in
:data:`os.environ`).
This only works reliable, if the current process is called from an
udev rule, and is usually used for tools executed from ``IMPORT=``
rules. Use this method to create device objects in Python scripts
called from udev rules.
``context`` is the library :class:`Context`.
Return a :class:`Device` object constructed from the environment.
Raise :exc:`DeviceNotFoundInEnvironmentError`, if no device could be
created from the environment.
.. udevversion:: 152
.. versionadded:: 0.18
"""
device = context._libudev.udev_device_new_from_environment(context)
if not device:
raise DeviceNotFoundInEnvironmentError()
return Device(context, device)
@classmethod
def METHODS(cls): # pylint: disable=invalid-name
"""
Return methods that obtain a :class:`Device` from a variety of
different data.
:return: a list of from_* methods.
:rtype: list of class methods
.. versionadded:: 0.18
"""
return [ #pragma: no cover
cls.from_device_file,
cls.from_device_number,
cls.from_name,
cls.from_path,
cls.from_sys_path
]
class Device(Mapping):
# pylint: disable=too-many-public-methods
"""
A single device with attached attributes and properties.
This class subclasses the ``Mapping`` ABC, providing a read-only
dictionary mapping property names to the corresponding values.
Therefore all well-known dicitionary methods and operators
(e.g. ``.keys()``, ``.items()``, ``in``) are available to access device
properties.
Aside of the properties, a device also has a set of udev-specific
attributes like the path inside ``sysfs``.
:class:`Device` objects compare equal and unequal to other devices and
to strings (based on :attr:`device_path`). However, there is no
ordering on :class:`Device` objects, and the corresponding operators
``>``, ``<``, ``<=`` and ``>=`` raise :exc:`~exceptions.TypeError`.
.. warning::
**Never** use object identity (``is`` operator) to compare
:class:`Device` objects. :mod:`pyudev` may create multiple
:class:`Device` objects for the same device. Instead compare
devices by value using ``==`` or ``!=``.
:class:`Device` objects are hashable and can therefore be used as keys
in dictionaries and sets.
They can also be given directly as ``udev_device *`` to functions wrapped
through :mod:`ctypes`.
"""
@classmethod
def from_path(cls, context, path): #pragma: no cover
"""
.. versionadded:: 0.4
.. deprecated:: 0.18
Use :class:`Devices.from_path` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_path(context, path)
@classmethod
def from_sys_path(cls, context, sys_path): #pragma: no cover
"""
.. versionchanged:: 0.4
Raise :exc:`NoSuchDeviceError` instead of returning ``None``, if
no device was found for ``sys_path``.
.. versionchanged:: 0.5
Raise :exc:`DeviceNotFoundAtPathError` instead of
:exc:`NoSuchDeviceError`.
.. deprecated:: 0.18
Use :class:`Devices.from_sys_path` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_sys_path(context, sys_path)
@classmethod
def from_name(cls, context, subsystem, sys_name): #pragma: no cover
"""
.. versionadded:: 0.5
.. deprecated:: 0.18
Use :class:`Devices.from_name` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_name(context, subsystem, sys_name)
@classmethod
def from_device_number(cls, context, typ, number): #pragma: no cover
"""
.. versionadded:: 0.11
.. deprecated:: 0.18
Use :class:`Devices.from_device_number` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_device_number(context, typ, number)
@classmethod
def from_device_file(cls, context, filename): #pragma: no cover
"""
.. versionadded:: 0.15
.. deprecated:: 0.18
Use :class:`Devices.from_device_file` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_device_file(context, filename)
@classmethod
def from_environment(cls, context): #pragma: no cover
"""
.. versionadded:: 0.6
.. deprecated:: 0.18
Use :class:`Devices.from_environment` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use equivalent Devices method instead.',
DeprecationWarning,
stacklevel=2
)
return Devices.from_environment(context)
def __init__(self, context, _device):
self.context = context
self._as_parameter_ = _device
self._libudev = context._libudev
def __del__(self):
self._libudev.udev_device_unref(self)
def __repr__(self):
return 'Device({0.sys_path!r})'.format(self)
@property
def parent(self):
"""
The parent :class:`Device` or ``None``, if there is no parent
device.
"""
parent = self._libudev.udev_device_get_parent(self)
if not parent:
return None
# the parent device is not referenced, thus forcibly acquire a
# reference
return Device(self.context, self._libudev.udev_device_ref(parent))
@property
def children(self):
"""
Yield all direct children of this device.
.. note::
In udev, parent-child relationships are generally ambiguous, i.e.
a parent can have multiple children, *and* a child can have multiple
parents. Hence, `child.parent == parent` does generally *not* hold
for all `child` objects in `parent.children`. In other words,
the :attr:`parent` of a device in this property can be different
from this device!
.. note::
As the underlying library does not provide any means to directly
query the children of a device, this property performs a linear
search through all devices.
Return an iterable yielding a :class:`Device` object for each direct
child of this device.
.. udevversion:: 172
.. versionchanged:: 0.13
Requires udev version 172 now.
"""
for device in self.context.list_devices().match_parent(self):
if device != self:
yield device
@property
def ancestors(self):
"""
Yield all ancestors of this device from bottom to top.
Return an iterator yielding a :class:`Device` object for each
ancestor of this device from bottom to top.
.. versionadded:: 0.16
"""
parent = self.parent
while parent is not None:
yield parent
parent = parent.parent
def find_parent(self, subsystem, device_type=None):
"""
Find the parent device with the given ``subsystem`` and
``device_type``.
``subsystem`` is a byte or unicode string containing the name of the
subsystem, in which to search for the parent. ``device_type`` is a
byte or unicode string holding the expected device type of the parent.
It can be ``None`` (the default), which means, that no specific device
type is expected.
Return a parent :class:`Device` within the given ``subsystem`` and, if
``device_type`` is not ``None``, with the given ``device_type``, or
``None``, if this device has no parent device matching these
constraints.
.. versionadded:: 0.9
"""
subsystem = ensure_byte_string(subsystem)
if device_type is not None:
device_type = ensure_byte_string(device_type)
parent = self._libudev.udev_device_get_parent_with_subsystem_devtype(
self, subsystem, device_type)
if not parent:
return None
# parent device is not referenced, thus forcibly acquire a reference
return Device(self.context, self._libudev.udev_device_ref(parent))
def traverse(self):
"""
Traverse all parent devices of this device from bottom to top.
Return an iterable yielding all parent devices as :class:`Device`
objects, *not* including the current device. The last yielded
:class:`Device` is the top of the device hierarchy.
.. deprecated:: 0.16
Will be removed in 1.0. Use :attr:`ancestors` instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use Device.ancestors instead.',
DeprecationWarning,
stacklevel=2
)
return self.ancestors
@property
def sys_path(self):
"""
Absolute path of this device in ``sysfs`` including the ``sysfs``
mount point as unicode string.
"""
return ensure_unicode_string(
self._libudev.udev_device_get_syspath(self))
@property
def device_path(self):
"""
Kernel device path as unicode string. This path uniquely identifies
a single device.
Unlike :attr:`sys_path`, this path does not contain the ``sysfs``
mount point. However, the path is absolute and starts with a slash
``'/'``.
"""
return ensure_unicode_string(
self._libudev.udev_device_get_devpath(self))
@property
def subsystem(self):
"""
Name of the subsystem this device is part of as unicode string.
:returns: name of subsystem if found, else None
:rtype: unicode string or NoneType
"""
subsys = self._libudev.udev_device_get_subsystem(self)
return None if subsys is None else ensure_unicode_string(subsys)
@property
def sys_name(self):
"""
Device file name inside ``sysfs`` as unicode string.
"""
return ensure_unicode_string(
self._libudev.udev_device_get_sysname(self))
@property
def sys_number(self):
"""
The trailing number of the :attr:`sys_name` as unicode string, or
``None``, if the device has no trailing number in its name.
.. note::
The number is returned as unicode string to preserve the exact
format of the number, especially any leading zeros:
>>> from pyudev import Context, Device
>>> context = Context()
>>> device = Devices.from_path(context, '/sys/devices/LNXSYSTM:00')
>>> device.sys_number
u'00'
To work with numbers, explicitly convert them to ints:
>>> int(device.sys_number)
0
.. versionadded:: 0.11
"""
number = self._libudev.udev_device_get_sysnum(self)
return ensure_unicode_string(number) if number is not None else None
@property
def device_type(self):
"""
Device type as unicode string, or ``None``, if the device type is
unknown.
>>> from pyudev import Context
>>> context = Context()
>>> for device in context.list_devices(subsystem='net'):
... '{0} - {1}'.format(device.sys_name, device.device_type or 'ethernet')
...
u'eth0 - ethernet'
u'wlan0 - wlan'
u'lo - ethernet'
u'vboxnet0 - ethernet'
.. versionadded:: 0.10
"""
device_type = self._libudev.udev_device_get_devtype(self)
if device_type is not None:
return ensure_unicode_string(device_type)
else:
return device_type
@property
def driver(self):
"""
The driver name as unicode string, or ``None``, if there is no
driver for this device.
.. versionadded:: 0.5
"""
driver = self._libudev.udev_device_get_driver(self)
return ensure_unicode_string(driver) if driver is not None else None
@property
def device_node(self):
"""
Absolute path to the device node of this device as unicode string or
``None``, if this device doesn't have a device node. The path
includes the device directory (see :attr:`Context.device_path`).
This path always points to the actual device node associated with
this device, and never to any symbolic links to this device node.
See :attr:`device_links` to get a list of symbolic links to this
device node.
.. warning::
For devices created with :meth:`from_device_file()`, the value of
this property is not necessary equal to the ``filename`` given to
:meth:`from_device_file()`.
"""
node = self._libudev.udev_device_get_devnode(self)
return ensure_unicode_string(node) if node is not None else None
@property
def device_number(self):
"""
The device number of the associated device as integer, or ``0``, if no
device number is associated.
Use :func:`os.major` and :func:`os.minor` to decompose the device
number into its major and minor number:
>>> import os
>>> from pyudev import Context, Device
>>> context = Context()
>>> sda = Devices.from_name(context, 'block', 'sda')
>>> sda.device_number
2048L
>>> (os.major(sda.device_number), os.minor(sda.device_number))
(8, 0)
For devices with an associated :attr:`device_node`, this is the same as
the ``st_rdev`` field of the stat result of the :attr:`device_node`:
>>> os.stat(sda.device_node).st_rdev
2048
.. versionadded:: 0.11
"""
return self._libudev.udev_device_get_devnum(self)
@property
def is_initialized(self):
"""
``True``, if the device is initialized, ``False`` otherwise.
A device is initialized, if udev has already handled this device and
has set up device node permissions and context, or renamed a network
device.
Consequently, this property is only implemented for devices with a
device node or for network devices. On all other devices this property
is always ``True``.
It is *not* recommended, that you use uninitialized devices.
.. seealso:: :attr:`time_since_initialized`
.. udevversion:: 165
.. versionadded:: 0.8
"""
return bool(self._libudev.udev_device_get_is_initialized(self))
@property
def time_since_initialized(self):
"""
The time elapsed since initialization as :class:`~datetime.timedelta`.
This property is only implemented on devices, which need to store
properties in the udev database. On all other devices this property is
simply zero :class:`~datetime.timedelta`.
.. seealso:: :attr:`is_initialized`
.. udevversion:: 165
.. versionadded:: 0.8
"""
microseconds = self._libudev.udev_device_get_usec_since_initialized(
self)
return timedelta(microseconds=microseconds)
@property
def device_links(self):
"""
An iterator, which yields the absolute paths (including the device
directory, see :attr:`Context.device_path`) of all symbolic links
pointing to the :attr:`device_node` of this device. The paths are
unicode strings.
UDev can create symlinks to the original device node (see
:attr:`device_node`) inside the device directory. This is often
used to assign a constant, fixed device node to devices like
removeable media, which technically do not have a constant device
node, or to map a single device into multiple device hierarchies.
The property provides access to all such symbolic links, which were
created by UDev for this device.
.. warning::
Links are not necessarily resolved by
:meth:`Devices.from_device_file()`. Hence do *not* rely on
``Devices.from_device_file(context, link).device_path ==
device.device_path`` from any ``link`` in ``device.device_links``.
"""
devlinks = self._libudev.udev_device_get_devlinks_list_entry(self)
for name, _ in udev_list_iterate(self._libudev, devlinks):
yield ensure_unicode_string(name)
@property
def action(self):
"""
The device event action as string, or ``None``, if this device was not
received from a :class:`Monitor`.
Usual actions are:
``'add'``
A device has been added (e.g. a USB device was plugged in)
``'remove'``
A device has been removed (e.g. a USB device was unplugged)
``'change'``
Something about the device changed (e.g. a device property)
``'online'``
The device is online now
``'offline'``
The device is offline now
.. warning::
Though the actions listed above are the most common, this property
*may* return other values, too, so be prepared to handle unknown
actions!
.. versionadded:: 0.16
"""
action = self._libudev.udev_device_get_action(self)
return ensure_unicode_string(action) if action is not None else None
@property
def sequence_number(self):
"""
The device event sequence number as integer, or ``0`` if this device
has no sequence number, i.e. was not received from a :class:`Monitor`.
.. versionadded:: 0.16
"""
return self._libudev.udev_device_get_seqnum(self)
@property
def attributes(self):
"""
The system attributes of this device as read-only
:class:`Attributes` mapping.
System attributes are basically normal files inside the the device
directory. These files contain all sorts of information about the
device, which may not be reflected by properties. These attributes
are commonly used for matching in udev rules, and can be printed
using ``udevadm info --attribute-walk``.
The values of these attributes are not always proper strings, and
can contain arbitrary bytes.
.. versionadded:: 0.5
"""
# do *not* cache the created object in an attribute of this class.
# Doing so creates an uncollectable reference cycle between Device and
# Attributes, because Attributes refers to this object through
# Attributes.device.
return Attributes(self)
@property
def properties(self):
"""
The udev properties of this object as read-only Properties mapping.
.. versionadded:: 0.21
"""
return Properties(self)
@property
def tags(self):
"""
A :class:`Tags` object representing the tags attached to this device.
The :class:`Tags` object supports a test for a single tag as well as
iteration over all tags:
>>> from pyudev import Context
>>> context = Context()
>>> device = next(iter(context.list_devices(tag='systemd')))
>>> 'systemd' in device.tags
True
>>> list(device.tags)
[u'seat', u'systemd', u'uaccess']
Tags are arbitrary classifiers that can be attached to devices by udev
scripts and daemons. For instance, systemd_ uses tags for multi-seat_
support.
.. _systemd: http://freedesktop.org/wiki/Software/systemd
.. _multi-seat: http://www.freedesktop.org/wiki/Software/systemd/multiseat
.. udevversion:: 154
.. versionadded:: 0.6
.. versionchanged:: 0.13
Return a :class:`Tags` object now.
"""
return Tags(self)
def __iter__(self):
"""
Iterate over the names of all properties defined for this device.
Return a generator yielding the names of all properties of this
device as unicode strings.
.. deprecated:: 0.21
Will be removed in 1.0. Access properties with Device.properties.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Access properties with Device.properties.',
DeprecationWarning,
stacklevel=2
)
return self.properties.__iter__()
def __len__(self):
"""
Return the amount of properties defined for this device as integer.
.. deprecated:: 0.21
Will be removed in 1.0. Access properties with Device.properties.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Access properties with Device.properties.',
DeprecationWarning,
stacklevel=2
)
return self.properties.__len__()
def __getitem__(self, prop):
"""
Get the given property from this device.
``prop`` is a unicode or byte string containing the name of the
property.
Return the property value as unicode string, or raise a
:exc:`~exceptions.KeyError`, if the given property is not defined
for this device.
.. deprecated:: 0.21
Will be removed in 1.0. Access properties with Device.properties.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Access properties with Device.properties.',
DeprecationWarning,
stacklevel=2
)
return self.properties.__getitem__(prop)
def asint(self, prop):
"""
Get the given property from this device as integer.
``prop`` is a unicode or byte string containing the name of the
property.
Return the property value as integer. Raise a
:exc:`~exceptions.KeyError`, if the given property is not defined
for this device, or a :exc:`~exceptions.ValueError`, if the property
value cannot be converted to an integer.
.. deprecated:: 0.21
Will be removed in 1.0. Use Device.properties.asint() instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use Device.properties.asint instead.',
DeprecationWarning,
stacklevel=2
)
return self.properties.asint(prop)
def asbool(self, prop):
"""
Get the given property from this device as boolean.
A boolean property has either a value of ``'1'`` or of ``'0'``,
where ``'1'`` stands for ``True``, and ``'0'`` for ``False``. Any
other value causes a :exc:`~exceptions.ValueError` to be raised.
``prop`` is a unicode or byte string containing the name of the
property.
Return ``True``, if the property value is ``'1'`` and ``False``, if
the property value is ``'0'``. Any other value raises a
:exc:`~exceptions.ValueError`. Raise a :exc:`~exceptions.KeyError`,
if the given property is not defined for this device.
.. deprecated:: 0.21
Will be removed in 1.0. Use Device.properties.asbool() instead.
"""
import warnings
warnings.warn(
'Will be removed in 1.0. Use Device.properties.asbool instead.',
DeprecationWarning,
stacklevel=2
)
return self.properties.asbool(prop)
def __hash__(self):
return hash(self.device_path)
def __eq__(self, other):
if isinstance(other, Device):
return self.device_path == other.device_path
else:
return self.device_path == other
def __ne__(self, other):
if isinstance(other, Device):
return self.device_path != other.device_path
else:
return self.device_path != other
def __gt__(self, other):
raise TypeError('Device not orderable')
def __lt__(self, other):
raise TypeError('Device not orderable')
def __le__(self, other):
raise TypeError('Device not orderable')
def __ge__(self, other):
raise TypeError('Device not orderable')
class Properties(Mapping):
"""
udev properties :class:`Device` objects.
.. versionadded:: 0.21
"""
def __init__(self, device):
self.device = device
self._libudev = device._libudev
def __iter__(self):
"""
Iterate over the names of all properties defined for the device.
Return a generator yielding the names of all properties of this
device as unicode strings.
"""
properties = \
self._libudev.udev_device_get_properties_list_entry(self.device)
for name, _ in udev_list_iterate(self._libudev, properties):
yield ensure_unicode_string(name)
def __len__(self):
"""
Return the amount of properties defined for this device as integer.
"""
properties = \
self._libudev.udev_device_get_properties_list_entry(self.device)
return sum(1 for _ in udev_list_iterate(self._libudev, properties))
def __getitem__(self, prop):
"""
Get the given property from this device.
``prop`` is a unicode or byte string containing the name of the
property.
Return the property value as unicode string, or raise a
:exc:`~exceptions.KeyError`, if the given property is not defined
for this device.
"""
value = self._libudev.udev_device_get_property_value(
self.device,
ensure_byte_string(prop)
)
if value is None:
raise KeyError(prop)
return ensure_unicode_string(value)
def asint(self, prop):
"""
Get the given property from this device as integer.
``prop`` is a unicode or byte string containing the name of the
property.
Return the property value as integer. Raise a
:exc:`~exceptions.KeyError`, if the given property is not defined
for this device, or a :exc:`~exceptions.ValueError`, if the property
value cannot be converted to an integer.
"""
return int(self[prop])
def asbool(self, prop):
"""
Get the given property from this device as boolean.
A boolean property has either a value of ``'1'`` or of ``'0'``,
where ``'1'`` stands for ``True``, and ``'0'`` for ``False``. Any
other value causes a :exc:`~exceptions.ValueError` to be raised.
``prop`` is a unicode or byte string containing the name of the
property.
Return ``True``, if the property value is ``'1'`` and ``False``, if
the property value is ``'0'``. Any other value raises a
:exc:`~exceptions.ValueError`. Raise a :exc:`~exceptions.KeyError`,
if the given property is not defined for this device.
"""
return string_to_bool(self[prop])
class Attributes(object):
"""
udev attributes for :class:`Device` objects.
.. versionadded:: 0.5
"""
def __init__(self, device):
self.device = device
self._libudev = device._libudev
@property
def available_attributes(self):
"""
Yield the ``available`` attributes for the device.
It is not guaranteed that a key in this list will have a value.
It is not guaranteed that a key not in this list will not have a value.
It is guaranteed that the keys in this list are the keys that libudev
considers to be "available" attributes.
If libudev version does not define udev_device_get_sysattr_list_entry()
yields nothing.
See rhbz#1267584.
"""
if not hasattr(self._libudev, 'udev_device_get_sysattr_list_entry'):
return # pragma: no cover
attrs = self._libudev.udev_device_get_sysattr_list_entry(self.device)
for attribute, _ in udev_list_iterate(self._libudev, attrs):
yield ensure_unicode_string(attribute)
def _get(self, attribute):
"""
Get the given system ``attribute`` for the device.
:param attribute: the key for an attribute value
:type attribute: unicode or byte string
:returns: the value corresponding to ``attribute``
:rtype: an arbitrary sequence of bytes
:raises KeyError: if no value found
"""
value = self._libudev.udev_device_get_sysattr_value(
self.device,
ensure_byte_string(attribute)
)
if value is None:
raise KeyError(attribute)
return value
def get(self, attribute, default=None):
"""
Get the given system ``attribute`` for the device.
:param attribute: the key for an attribute value
:type attribute: unicode or byte string
:param default: a default if no corresponding value found
:type default: a sequence of bytes
:returns: the value corresponding to ``attribute`` or ``default``
:rtype: object
"""
try:
return self._get(attribute)
except KeyError:
return default
def asstring(self, attribute):
"""
Get the given ``attribute`` for the device as unicode string.
:param attribute: the key for an attribute value
:type attribute: unicode or byte string
:returns: the value corresponding to ``attribute``, as unicode
:rtype: unicode
:raises KeyError: if no value found for ``attribute``
:raises UnicodeDecodeError: if value is not convertible
"""
return ensure_unicode_string(self._get(attribute))
def asint(self, attribute):
"""
Get the given ``attribute`` as an int.
:param attribute: the key for an attribute value
:type attribute: unicode or byte string
:returns: the value corresponding to ``attribute``, as an int
:rtype: int
:raises KeyError: if no value found for ``attribute``
:raises UnicodeDecodeError: if value is not convertible to unicode
:raises ValueError: if unicode value can not be converted to an int
"""
return int(self.asstring(attribute))
def asbool(self, attribute):
"""
Get the given ``attribute`` from this device as a bool.
:param attribute: the key for an attribute value
:type attribute: unicode or byte string
:returns: the value corresponding to ``attribute``, as bool
:rtype: bool
:raises KeyError: if no value found for ``attribute``
:raises UnicodeDecodeError: if value is not convertible to unicode
:raises ValueError: if unicode value can not be converted to a bool
A boolean attribute has either a value of ``'1'`` or of ``'0'``,
where ``'1'`` stands for ``True``, and ``'0'`` for ``False``. Any
other value causes a :exc:`~exceptions.ValueError` to be raised.
"""
return string_to_bool(self.asstring(attribute))
class Tags(Iterable, Container):
"""
A iterable over :class:`Device` tags.
Subclasses the ``Container`` and the ``Iterable`` ABC.
"""
# pylint: disable=too-few-public-methods
def __init__(self, device):
self.device = device
self._libudev = device._libudev
def _has_tag(self, tag):
"""
Whether ``tag`` exists.
:param tag: unicode string with name of tag
:rtype: bool
"""
if hasattr(self._libudev, 'udev_device_has_tag'):
return bool(self._libudev.udev_device_has_tag(
self.device, ensure_byte_string(tag)))
else: # pragma: no cover
return any(t == tag for t in self)
def __contains__(self, tag):
"""
Check for existence of ``tag``.
``tag`` is a tag as unicode string.
Return ``True``, if ``tag`` is attached to the device, ``False``
otherwise.
"""
return self._has_tag(tag)
def __iter__(self):
"""
Iterate over all tags.
Yield each tag as unicode string.
"""
tags = self._libudev.udev_device_get_tags_list_entry(self.device)
for tag, _ in udev_list_iterate(self._libudev, tags):
yield ensure_unicode_string(tag)
pyudev-0.21.0/src/pyudev/device/__init__.py 0000664 0001750 0001750 00000002517 12710747516 021750 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.device
=============
Device class implementation of :mod:`pyudev`.
.. moduleauthor:: Sebastian Wiesner
"""
from ._device import Attributes
from ._device import Device
from ._device import Devices
from ._device import Tags
from ._errors import DeviceNotFoundAtPathError
from ._errors import DeviceNotFoundByFileError
from ._errors import DeviceNotFoundByNameError
from ._errors import DeviceNotFoundByNumberError
from ._errors import DeviceNotFoundError
from ._errors import DeviceNotFoundInEnvironmentError
pyudev-0.21.0/src/pyudev/device/_errors.py 0000664 0001750 0001750 00000012154 12677232426 021663 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.device._errors
=====================
Errors raised by Device methods.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import abc
from six import add_metaclass
@add_metaclass(abc.ABCMeta)
class DeviceError(Exception):
"""
Any error raised when messing around w/ or trying to discover devices.
"""
@add_metaclass(abc.ABCMeta)
class DeviceNotFoundError(DeviceError):
"""
An exception indicating that no :class:`Device` was found.
.. versionchanged:: 0.5
Rename from ``NoSuchDeviceError`` to its current name.
"""
class DeviceNotFoundAtPathError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found at a given path.
"""
def __init__(self, sys_path):
DeviceNotFoundError.__init__(self, sys_path)
@property
def sys_path(self):
"""
The path that caused this error as string.
"""
return self.args[0]
def __str__(self):
return 'No device at {0!r}'.format(self.sys_path)
class DeviceNotFoundByFileError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found from the given filename.
"""
class DeviceNotFoundByInterfaceIndexError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was found
from the given interface index.
"""
class DeviceNotFoundByKernelDeviceError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was found
from the given kernel device string.
The format of the kernel device string is defined in the
systemd.journal-fields man pagees.
"""
class DeviceNotFoundByNameError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating that no :class:`Device` was
found with a given name.
"""
def __init__(self, subsystem, sys_name):
DeviceNotFoundError.__init__(self, subsystem, sys_name)
@property
def subsystem(self):
"""
The subsystem that caused this error as string.
"""
return self.args[0]
@property
def sys_name(self):
"""
The sys name that caused this error as string.
"""
return self.args[1]
def __str__(self):
return 'No device {0.sys_name!r} in {0.subsystem!r}'.format(self)
class DeviceNotFoundByNumberError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating, that no :class:`Device` was found
for a given device number.
"""
def __init__(self, typ, number):
DeviceNotFoundError.__init__(self, typ, number)
@property
def device_type(self):
"""
The device type causing this error as string. Either ``'char'`` or
``'block'``.
"""
return self.args[0]
@property
def device_number(self):
"""
The device number causing this error as integer.
"""
return self.args[1]
def __str__(self):
return ('No {0.device_type} device with number '
'{0.device_number}'.format(self))
class DeviceNotFoundInEnvironmentError(DeviceNotFoundError):
"""
A :exc:`DeviceNotFoundError` indicating, that no :class:`Device` could
be constructed from the process environment.
"""
def __str__(self):
return 'No device found in environment'
class DeviceValueError(DeviceError):
"""
Raised when a parameter has an unacceptable value.
May also be raised when the parameter has an unacceptable type.
"""
_FMT_STR = "value '%s' for parameter %s is unacceptable"
def __init__(self, value, param, msg=None):
""" Initializer.
:param object value: the value
:param str param: the parameter
:param str msg: an explanatory message
"""
# pylint: disable=super-init-not-called
self._value = value
self._param = param
self._msg = msg
def __str__(self):
if self._msg:
fmt_str = self._FMT_STR + ": %s"
return fmt_str % (self._value, self._param, self._msg)
else:
return self._FMT_STR % (self._value, self._param)
pyudev-0.21.0/src/pyudev/discover.py 0000664 0001750 0001750 00000026364 12744230565 020574 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2015 mulhern
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.discover
===============
Tools to discover a device given limited information.
.. moduleauthor:: mulhern
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import abc
import functools
import os
import re
import six
from pyudev.device import Devices
from pyudev.device import DeviceNotFoundError
def wrap_exception(func):
"""
Allow Device discovery methods to return None instead of raising an
exception.
"""
@functools.wraps(func)
def the_func(*args, **kwargs):
"""
Returns result of calling ``func`` on ``args``, ``kwargs``.
Returns None if ``func`` raises :exc:`DeviceNotFoundError`.
"""
try:
return func(*args, **kwargs)
except DeviceNotFoundError:
return None
return the_func
@six.add_metaclass(abc.ABCMeta)
class Hypothesis(object):
"""
Represents a hypothesis about the meaning of the device identifier.
"""
@classmethod
@abc.abstractmethod
def match(cls, value): # pragma: no cover
"""
Match the given string according to the hypothesis.
The purpose of this method is to obtain a value corresponding to
``value`` if that is possible. It may use a regular expression, but
in general it should just return ``value`` and let the lookup method
sort out the rest.
:param str value: the string to inspect
:returns: the matched thing or None if unmatched
:rtype: the type of lookup's key parameter or NoneType
"""
raise NotImplementedError()
@classmethod
@abc.abstractmethod
def lookup(cls, context, key): # pragma: no cover
"""
Lookup the given string according to the hypothesis.
:param Context context: the pyudev context
:param key: a key with which to lookup the device
:type key: the type of match's return value if not None
:returns: a list of Devices obtained
:rtype: frozenset of :class:`Device`
"""
raise NotImplementedError()
@classmethod
def setup(cls, context):
"""
A potentially expensive method that may allow an :class:`Hypothesis`
to find devices more rapidly or to find a device that it would
otherwise miss.
:param Context context: the pyudev context
"""
pass
@classmethod
def get_devices(cls, context, value):
"""
Get any devices that may correspond to the given string.
:param Context context: the pyudev context
:param str value: the value to look for
:returns: a list of devices obtained
:rtype: set of :class:`Device`
"""
key = cls.match(value)
return cls.lookup(context, key) if key is not None else frozenset()
class DeviceNumberHypothesis(Hypothesis):
"""
Represents the hypothesis that the device is a device number.
The device may be separated into major/minor number or a composite number.
"""
@classmethod
def _match_major_minor(cls, value):
"""
Match the number under the assumption that it is a major,minor pair.
:param str value: value to match
:returns: the device number or None
:rtype: int or NoneType
"""
major_minor_re = re.compile(
r'^(?P\d+)(\D+)(?P\d+)$'
)
match = major_minor_re.match(value)
return match and os.makedev(
int(match.group('major')),
int(match.group('minor'))
)
@classmethod
def _match_number(cls, value):
"""
Match the number under the assumption that it is a single number.
:param str value: value to match
:returns: the device number or None
:rtype: int or NoneType
"""
number_re = re.compile(r'^(?P\d+)$')
match = number_re.match(value)
return match and int(match.group('number'))
@classmethod
def match(cls, value):
"""
Match the number under the assumption that it is a device number.
:returns: the device number or None
:rtype: int or NoneType
"""
return cls._match_major_minor(value) or cls._match_number(value)
@classmethod
def find_subsystems(cls, context):
"""
Find subsystems in /sys/dev.
:param Context context: the context
:returns: a lis of available subsystems
:rtype: list of str
"""
sys_path = context.sys_path
return os.listdir(os.path.join(sys_path, 'dev'))
@classmethod
def lookup(cls, context, key):
"""
Lookup by the device number.
:param Context context: the context
:param int key: the device number
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
func = wrap_exception(Devices.from_device_number)
res = (func(context, s, key) for s in cls.find_subsystems(context))
return frozenset(r for r in res if r is not None)
class DevicePathHypothesis(Hypothesis):
"""
Discover the device assuming the identifier is a device path.
"""
@classmethod
def match(cls, value):
"""
Match ``value`` under the assumption that it is a device path.
:returns: the device path or None
:rtype: str or NoneType
"""
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup by the path.
:param Context context: the context
:param str key: the device path
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
res = wrap_exception(Devices.from_path)(context, key)
return frozenset((res,)) if res is not None else frozenset()
class DeviceNameHypothesis(Hypothesis):
"""
Discover the device assuming the input is a device name.
Try every available subsystem.
"""
@classmethod
def find_subsystems(cls, context):
"""
Find all subsystems in sysfs.
:param Context context: the context
:rtype: frozenset
:returns: subsystems in sysfs
"""
sys_path = context.sys_path
dirnames = ('bus', 'class', 'subsystem')
absnames = (os.path.join(sys_path, name) for name in dirnames)
realnames = (d for d in absnames if os.path.isdir(d))
return frozenset(n for d in realnames for n in os.listdir(d))
@classmethod
def match(cls, value):
"""
Match ``value`` under the assumption that it is a device name.
:returns: the device path or None
:rtype: str or NoneType
"""
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup by the path.
:param Context context: the context
:param str key: the device path
:returns: a list of matching devices
:rtype: frozenset of :class:`Device`
"""
func = wrap_exception(Devices.from_name)
res = (func(context, s, key) for s in cls.find_subsystems(context))
return frozenset(r for r in res if r is not None)
class DeviceFileHypothesis(Hypothesis):
"""
Discover the device assuming the value is some portion of a device file.
The device file may be a link to a device node.
"""
_LINK_DIRS = [
'/dev',
'/dev/disk/by-id',
'/dev/disk/by-label',
'/dev/disk/by-partlabel',
'/dev/disk/by-partuuid',
'/dev/disk/by-path',
'/dev/disk/by-uuid',
'/dev/input/by-path',
'/dev/mapper',
'/dev/md',
'/dev/vg'
]
@classmethod
def get_link_dirs(cls, context):
"""
Get all directories that may contain links to device nodes.
This method checks the device links of every device, so it is very
expensive.
:param Context context: the context
:returns: a sorted list of directories that contain device links
:rtype: list
"""
devices = context.list_devices()
devices_with_links = (d for d in devices if list(d.device_links))
links = (l for d in devices_with_links for l in d.device_links)
return sorted(set(os.path.dirname(l) for l in links))
@classmethod
def setup(cls, context):
"""
Set the link directories to be used when discovering by file.
Uses `get_link_dirs`, so is as expensive as it is.
:param Context context: the context
"""
cls._LINK_DIRS = cls.get_link_dirs(context)
@classmethod
def match(cls, value):
return value
@classmethod
def lookup(cls, context, key):
"""
Lookup the device under the assumption that the key is part of
the name of a device file.
:param Context context: the context
:param str key: a portion of the device file name
It is assumed that either it is the whole name of the device file
or it is the basename.
A device file may be a device node or a device link.
"""
func = wrap_exception(Devices.from_device_file)
if '/' in key:
device = func(context, key)
return frozenset((device,)) if device is not None else frozenset()
else:
files = (os.path.join(ld, key) for ld in cls._LINK_DIRS)
devices = (func(context, f) for f in files)
return frozenset(d for d in devices if d is not None)
class Discovery(object):
# pylint: disable=too-few-public-methods
"""
Provides discovery methods for devices.
"""
_HYPOTHESES = [
DeviceFileHypothesis,
DeviceNameHypothesis,
DeviceNumberHypothesis,
DevicePathHypothesis
]
def __init__(self):
self._hypotheses = self._HYPOTHESES
def setup(self, context):
"""
Set up individual hypotheses.
May be an expensive call.
:param Context context: the context
"""
for hyp in self._hypotheses:
hyp.setup(context)
def get_devices(self, context, value):
"""
Get the devices corresponding to value.
:param Context context: the context
:param str value: some identifier of the device
:returns: a list of corresponding devices
:rtype: frozenset of :class:`Device`
"""
return frozenset(
d for h in self._hypotheses for d in h.get_devices(context, value)
)
pyudev-0.21.0/src/pyudev/wx.py 0000664 0001750 0001750 00000010560 12654153421 017376 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""pyudev.wx
=========
Wx integration.
:class:`MonitorObserver` integrates device monitoring into the wxPython\_
mainloop by turing device events into wx events.
:mod:`wx` from wxPython\_ must be available when importing this module.
.. _wxPython: http://wxpython.org/
.. moduleauthor:: Tobias Eberle
.. versionadded:: 0.14
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from wx import EvtHandler, PostEvent
from wx.lib.newevent import NewEvent
import pyudev
DeviceEvent, EVT_DEVICE_EVENT = NewEvent()
class MonitorObserver(EvtHandler):
"""
An observer for device events integrating into the :mod:`wx` mainloop.
This class inherits :class:`~wx.EvtHandler` to turn device events into
wx events:
>>> from pyudev import Context, Monitor
>>> from pyudev.wx import MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> observer = MonitorObserver(monitor)
>>> def device_event(event):
... print('action {0} on device {1}'.format(event.device.action, event.device))
>>> observer.Bind(EVT_DEVICE_EVENT, device_event)
>>> monitor.start()
This class is a child of :class:`wx.EvtHandler`.
.. versionadded:: 0.17
"""
def __init__(self, monitor):
EvtHandler.__init__(self)
self.monitor = monitor
self._observer_thread = None
self.start()
@property
def enabled(self):
"""
Whether this observer is enabled or not.
If ``True`` (the default), this observer is enabled, and emits events.
Otherwise it is disabled and does not emit any events.
"""
return self._observer_thread is not None
@enabled.setter
def enabled(self, value):
if value:
self.start()
else:
self.stop()
def start(self):
"""
Enable this observer.
Do nothing, if the observer is already enabled.
"""
if self._observer_thread is not None:
return
self._observer_thread = pyudev.MonitorObserver(
self.monitor, callback=self._emit_event,
name='wx-observer-thread')
self._observer_thread.start()
def stop(self):
"""
Disable this observer.
Do nothing, if the observer is already disabled.
"""
if self._observer_thread is None:
return
self._observer_thread.stop()
def _emit_event(self, device):
PostEvent(self, DeviceEvent(device=device))
DeviceAddedEvent, EVT_DEVICE_ADDED = NewEvent()
DeviceRemovedEvent, EVT_DEVICE_REMOVED = NewEvent()
DeviceChangedEvent, EVT_DEVICE_CHANGED = NewEvent()
DeviceMovedEvent, EVT_DEVICE_MOVED = NewEvent()
class WxUDevMonitorObserver(MonitorObserver):
"""An observer for device events integrating into the :mod:`wx` mainloop.
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
_action_event_map = {
'add': DeviceAddedEvent,
'remove': DeviceRemovedEvent,
'change': DeviceChangedEvent,
'move': DeviceMovedEvent
}
def __init__(self, monitor):
MonitorObserver.__init__(self, monitor)
import warnings
warnings.warn('Will be removed in 1.0. '
'Use pyudev.wx.MonitorObserver instead.',
DeprecationWarning)
def _emit_event(self, device):
PostEvent(self, DeviceEvent(action=device.action, device=device))
event_class = self._action_event_map.get(device.action)
if event_class is not None:
PostEvent(self, event_class(device=device))
pyudev-0.21.0/src/pyudev/_compat.py 0000664 0001750 0001750 00000002674 12654153421 020371 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2011 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev._compat
==============
Compatibility for Python versions, that lack certain functions.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
from subprocess import Popen, CalledProcessError, PIPE
def check_output(command):
"""
Compatibility with :func:`subprocess.check_output` from Python 2.7 and
upwards.
"""
proc = Popen(command, stdout=PIPE)
output = proc.communicate()[0]
if proc.returncode != 0:
raise CalledProcessError(proc.returncode, command)
return output
pyudev-0.21.0/src/pyudev/pyside.py 0000664 0001750 0001750 00000003670 12660444030 020235 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# pylint: disable=anomalous-backslash-in-string
"""
pyudev.pyside
=============
PySide integration.
:class:`QUDevMonitorObserver` integrates device monitoring into the PySide\_
mainloop by turing device events into Qt signals.
:mod:`PySide.QtCore` from PySide\_ must be available when importing this
module.
.. _PySide: http://www.pyside.org
.. moduleauthor:: Sebastian Wiesner
.. versionadded:: 0.6
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from PySide import QtCore
from ._qt_base import MonitorObserverGenerator
from ._qt_base import QUDevMonitorObserverGenerator
# pylint: disable=invalid-name
MonitorObserver = MonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.Signal,
QtCore.QSocketNotifier
)
"""
.. deprecated:: 0.17
Will be removed in 1.0. Use :class:`MonitorObserver` instead.
"""
QUDevMonitorObserver = QUDevMonitorObserverGenerator.make_monitor_observer(
QtCore.QObject,
QtCore.Signal,
QtCore.QSocketNotifier
)
pyudev-0.21.0/src/pyudev/monitor.py 0000664 0001750 0001750 00000050730 12744230565 020437 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
pyudev.monitor
==============
Monitor implementation.
.. moduleauthor:: Sebastian Wiesner
"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os
import errno
from threading import Thread
from functools import partial
from pyudev.device import Device
from pyudev._util import eintr_retry_call
from pyudev._util import ensure_byte_string
from pyudev._os import pipe
from pyudev._os import poll
class Monitor(object):
"""
A synchronous device event monitor.
A :class:`Monitor` objects connects to the udev daemon and listens for
changes to the device list. A monitor is created by connecting to the
kernel daemon through netlink (see :meth:`from_netlink`):
>>> from pyudev import Context, Monitor
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
Once the monitor is created, you can add a filter using :meth:`filter_by()`
or :meth:`filter_by_tag()` to drop incoming events in subsystems, which are
not of interest to the application:
>>> monitor.filter_by('input')
When the monitor is eventually set up, you can either poll for events
synchronously:
>>> device = monitor.poll(timeout=3)
>>> if device:
... print('{0.action}: {0}'.format(device))
...
Or you can monitor events asynchronously with :class:`MonitorObserver`.
To integrate into various event processing frameworks, the monitor provides
a :func:`selectable ` file description by :meth:`fileno()`.
However, do *not* read or write directly on this file descriptor.
Instances of this class can directly be given as ``udev_monitor *`` to
functions wrapped through :mod:`ctypes`.
.. versionchanged:: 0.16
Remove :meth:`from_socket()` which is deprecated, and even removed in
recent udev versions.
"""
def __init__(self, context, monitor_p):
self.context = context
self._as_parameter_ = monitor_p
self._libudev = context._libudev
self._started = False
def __del__(self):
self._libudev.udev_monitor_unref(self)
@classmethod
def from_netlink(cls, context, source='udev'):
"""
Create a monitor by connecting to the kernel daemon through netlink.
``context`` is the :class:`Context` to use. ``source`` is a string,
describing the event source. Two sources are available:
``'udev'`` (the default)
Events emitted after udev as registered and configured the device.
This is the absolutely recommended source for applications.
``'kernel'``
Events emitted directly after the kernel has seen the device. The
device has not yet been configured by udev and might not be usable
at all. **Never** use this, unless you know what you are doing.
Return a new :class:`Monitor` object, which is connected to the
given source. Raise :exc:`~exceptions.ValueError`, if an invalid
source has been specified. Raise
:exc:`~exceptions.EnvironmentError`, if the creation of the monitor
failed.
"""
if source not in ('kernel', 'udev'):
raise ValueError('Invalid source: {0!r}. Must be one of "udev" '
'or "kernel"'.format(source))
monitor = context._libudev.udev_monitor_new_from_netlink(
context, ensure_byte_string(source))
if not monitor:
raise EnvironmentError('Could not create udev monitor')
return cls(context, monitor)
@property
def started(self):
"""
``True``, if this monitor was started, ``False`` otherwise. Readonly.
.. seealso:: :meth:`start()`
.. versionadded:: 0.16
"""
return self._started
def fileno(self):
# pylint: disable=anomalous-backslash-in-string
"""
Return the file description associated with this monitor as integer.
This is really a real file descriptor ;), which can be watched and
:func:`select.select`\ ed.
"""
return self._libudev.udev_monitor_get_fd(self)
def filter_by(self, subsystem, device_type=None):
"""
Filter incoming events.
``subsystem`` is a byte or unicode string with the name of a
subsystem (e.g. ``'input'``). Only events originating from the
given subsystem pass the filter and are handed to the caller.
If given, ``device_type`` is a byte or unicode string specifying the
device type. Only devices with the given device type are propagated
to the caller. If ``device_type`` is not given, no additional
filter for a specific device type is installed.
These filters are executed inside the kernel, and client processes
will usually not be woken up for device, that do not match these
filters.
.. versionchanged:: 0.15
This method can also be after :meth:`start()` now.
"""
subsystem = ensure_byte_string(subsystem)
if device_type is not None:
device_type = ensure_byte_string(device_type)
self._libudev.udev_monitor_filter_add_match_subsystem_devtype(
self, subsystem, device_type)
self._libudev.udev_monitor_filter_update(self)
def filter_by_tag(self, tag):
"""
Filter incoming events by the given ``tag``.
``tag`` is a byte or unicode string with the name of a tag. Only
events for devices which have this tag attached pass the filter and are
handed to the caller.
Like with :meth:`filter_by` this filter is also executed inside the
kernel, so that client processes are usually not woken up for devices
without the given ``tag``.
.. udevversion:: 154
.. versionadded:: 0.9
.. versionchanged:: 0.15
This method can also be after :meth:`start()` now.
"""
self._libudev.udev_monitor_filter_add_match_tag(
self, ensure_byte_string(tag))
self._libudev.udev_monitor_filter_update(self)
def remove_filter(self):
"""
Remove any filters installed with :meth:`filter_by()` or
:meth:`filter_by_tag()` from this monitor.
.. warning::
Up to udev 181 (and possibly even later versions) the underlying
``udev_monitor_filter_remove()`` seems to be broken. If used with
affected versions this method always raises
:exc:`~exceptions.ValueError`.
Raise :exc:`~exceptions.EnvironmentError` if removal of installed
filters failed.
.. versionadded:: 0.15
"""
self._libudev.udev_monitor_filter_remove(self)
self._libudev.udev_monitor_filter_update(self)
def enable_receiving(self):
"""
Switch the monitor into listing mode.
Connect to the event source and receive incoming events. Only after
calling this method, the monitor listens for incoming events.
.. note::
This method is implicitly called by :meth:`__iter__`. You don't
need to call it explicitly, if you are iterating over the
monitor.
.. deprecated:: 0.16
Will be removed in 1.0. Use :meth:`start()` instead.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use Monitor.start() instead.',
DeprecationWarning)
self.start()
def start(self):
"""
Start this monitor.
The monitor will not receive events until this method is called. This
method does nothing if called on an already started :class:`Monitor`.
.. note::
Typically you don't need to call this method. It is implicitly
called by :meth:`poll()` and :meth:`__iter__()`.
.. seealso:: :attr:`started`
.. versionchanged:: 0.16
This method does nothing if the :class:`Monitor` was already
started.
"""
if not self._started:
self._libudev.udev_monitor_enable_receiving(self)
# Force monitor FD into non-blocking mode
pipe.set_fd_status_flag(self, os.O_NONBLOCK)
self._started = True
def set_receive_buffer_size(self, size):
"""
Set the receive buffer ``size``.
``size`` is the requested buffer size in bytes, as integer.
.. note::
The CAP_NET_ADMIN capability must be contained in the effective
capability set of the caller for this method to succeed. Otherwise
:exc:`~exceptions.EnvironmentError` will be raised, with ``errno``
set to :data:`~errno.EPERM`. Unprivileged processes typically lack
this capability. You can check the capabilities of the current
process with the python-prctl_ module:
>>> import prctl
>>> prctl.cap_effective.net_admin
Raise :exc:`~exceptions.EnvironmentError`, if the buffer size could not
bet set.
.. versionadded:: 0.13
.. _python-prctl: http://packages.python.org/python-prctl
"""
self._libudev.udev_monitor_set_receive_buffer_size(self, size)
def _receive_device(self):
"""Receive a single device from the monitor.
Return the received :class:`Device`, or ``None`` if no device could be
received.
"""
while True:
try:
device_p = self._libudev.udev_monitor_receive_device(self)
return Device(self.context, device_p) if device_p else None
except EnvironmentError as error:
if error.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
# No data available
return None
elif error.errno == errno.EINTR:
# Try again if our system call was interrupted
continue
else:
raise
def poll(self, timeout=None):
"""
Poll for a device event.
You can use this method together with :func:`iter()` to synchronously
monitor events in the current thread::
for device in iter(monitor.poll, None):
print('{0.action} on {0.device_path}'.format(device))
Since this method will never return ``None`` if no ``timeout`` is
specified, this is effectively an endless loop. With
:func:`functools.partial()` you can also create a loop that only waits
for a specified time::
for device in iter(partial(monitor.poll, 3), None):
print('{0.action} on {0.device_path}'.format(device))
This loop will only wait three seconds for a new device event. If no
device event occurred after three seconds, the loop will exit.
``timeout`` is a floating point number that specifies a time-out in
seconds. If omitted or ``None``, this method blocks until a device
event is available. If ``0``, this method just polls and will never
block.
.. note::
This method implicitly calls :meth:`start()`.
Return the received :class:`Device`, or ``None`` if a timeout
occurred. Raise :exc:`~exceptions.EnvironmentError` if event retrieval
failed.
.. seealso::
:attr:`Device.action`
The action that created this event.
:attr:`Device.sequence_number`
The sequence number of this event.
.. versionadded:: 0.16
"""
if timeout is not None and timeout > 0:
# .poll() takes timeout in milliseconds
timeout = int(timeout * 1000)
self.start()
if eintr_retry_call(poll.Poll.for_events((self, 'r')).poll, timeout):
return self._receive_device()
else:
return None
def receive_device(self):
"""
Receive a single device from the monitor.
.. warning::
You *must* call :meth:`start()` before calling this method.
The caller must make sure, that there are events available in the
event queue. The call blocks, until a device is available.
If a device was available, return ``(action, device)``. ``device``
is the :class:`Device` object describing the device. ``action`` is
a string describing the action. Usual actions are:
``'add'``
A device has been added (e.g. a USB device was plugged in)
``'remove'``
A device has been removed (e.g. a USB device was unplugged)
``'change'``
Something about the device changed (e.g. a device property)
``'online'``
The device is online now
``'offline'``
The device is offline now
Raise :exc:`~exceptions.EnvironmentError`, if no device could be
read.
.. deprecated:: 0.16
Will be removed in 1.0. Use :meth:`Monitor.poll()` instead.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use Monitor.poll() instead.',
DeprecationWarning)
device = self.poll()
return device.action, device
def __iter__(self):
"""
Wait for incoming events and receive them upon arrival.
This methods implicitly calls :meth:`start()`, and starts polling the
:meth:`fileno` of this monitor. If a event comes in, it receives the
corresponding device and yields it to the caller.
The returned iterator is endless, and continues receiving devices
without ever stopping.
Yields ``(action, device)`` (see :meth:`receive_device` for a
description).
.. deprecated:: 0.16
Will be removed in 1.0. Use an explicit loop over :meth:`poll()`
instead, or monitor asynchronously with :class:`MonitorObserver`.
"""
import warnings
warnings.warn('Will be removed in 1.0. Use an explicit loop over '
'"poll()" instead, or monitor asynchronously with '
'"MonitorObserver".', DeprecationWarning)
self.start()
while True:
device = self.poll()
if device is not None:
yield device.action, device
class MonitorObserver(Thread):
"""
An asynchronous observer for device events.
This class subclasses :class:`~threading.Thread` class to asynchronously
observe a :class:`Monitor` in a background thread:
>>> from pyudev import Context, Monitor, MonitorObserver
>>> context = Context()
>>> monitor = Monitor.from_netlink(context)
>>> monitor.filter_by(subsystem='input')
>>> def print_device_event(device):
... print('background event {0.action}: {0.device_path}'.format(device))
>>> observer = MonitorObserver(monitor, callback=print_device_event, name='monitor-observer')
>>> observer.daemon
True
>>> observer.start()
In the above example, input device events will be printed in background,
until :meth:`stop()` is called on ``observer``.
.. note::
Instances of this class are always created as daemon thread. If you do
not want to use daemon threads for monitoring, you need explicitly set
:attr:`~threading.Thread.daemon` to ``False`` before invoking
:meth:`~threading.Thread.start()`.
.. seealso::
:attr:`Device.action`
The action that created this event.
:attr:`Device.sequence_number`
The sequence number of this event.
.. versionadded:: 0.14
.. versionchanged:: 0.15
:meth:`Monitor.start()` is implicitly called when the thread is started.
"""
def __init__(self, monitor, event_handler=None, callback=None, *args,
**kwargs):
"""
Create a new observer for the given ``monitor``.
``monitor`` is the :class:`Monitor` to observe. ``callback`` is the
callable to invoke on events, with the signature ``callback(device)``
where ``device`` is the :class:`Device` that caused the event.
.. warning::
``callback`` is invoked in the observer thread, hence the observer
is blocked while callback executes.
``args`` and ``kwargs`` are passed unchanged to the constructor of
:class:`~threading.Thread`.
.. deprecated:: 0.16
The ``event_handler`` argument will be removed in 1.0. Use
the ``callback`` argument instead.
.. versionchanged:: 0.16
Add ``callback`` argument.
"""
if callback is None and event_handler is None:
raise ValueError('callback missing')
elif callback is not None and event_handler is not None:
raise ValueError('Use either callback or event handler')
Thread.__init__(self, *args, **kwargs)
self.monitor = monitor
# observer threads should not keep the interpreter alive
self.daemon = True
self._stop_event = None
if event_handler is not None:
import warnings
warnings.warn('"event_handler" argument will be removed in 1.0. '
'Use Monitor.poll() instead.', DeprecationWarning)
callback = lambda d: event_handler(d.action, d)
self._callback = callback
def start(self):
"""Start the observer thread."""
if not self.is_alive():
self._stop_event = pipe.Pipe.open()
Thread.start(self)
def run(self):
self.monitor.start()
notifier = poll.Poll.for_events(
(self.monitor, 'r'), (self._stop_event.source, 'r'))
while True:
for file_descriptor, event in eintr_retry_call(notifier.poll):
if file_descriptor == self._stop_event.source.fileno():
# in case of a stop event, close our pipe side, and
# return from the thread
self._stop_event.source.close()
return
elif file_descriptor == self.monitor.fileno() and event == 'r':
read_device = partial(eintr_retry_call, self.monitor.poll, timeout=0)
for device in iter(read_device, None):
self._callback(device)
else:
raise EnvironmentError('Observed monitor hung up')
def send_stop(self):
"""
Send a stop signal to the background thread.
The background thread will eventually exit, but it may still be running
when this method returns. This method is essentially the asynchronous
equivalent to :meth:`stop()`.
.. note::
The underlying :attr:`monitor` is *not* stopped.
"""
if self._stop_event is None:
return
with self._stop_event.sink:
# emit a stop event to the thread
eintr_retry_call(self._stop_event.sink.write, b'\x01')
self._stop_event.sink.flush()
def stop(self):
"""
Synchronously stop the background thread.
.. note::
This method can safely be called from the observer thread. In this
case it is equivalent to :meth:`send_stop()`.
Send a stop signal to the backgroud (see :meth:`send_stop`), and waits
for the background thread to exit (see :meth:`~threading.Thread.join`)
if the current thread is *not* the observer thread.
After this method returns in a thread *that is not the observer
thread*, the ``callback`` is guaranteed to not be invoked again
anymore.
.. note::
The underlying :attr:`monitor` is *not* stopped.
.. versionchanged:: 0.16
This method can be called from the observer thread.
"""
self.send_stop()
try:
self.join()
except RuntimeError:
pass
pyudev-0.21.0/src/pyudev.egg-info/ 0000775 0001750 0001750 00000000000 12744231232 020053 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/src/pyudev.egg-info/PKG-INFO 0000664 0001750 0001750 00000012025 12744231230 021146 0 ustar mulhern mulhern 0000000 0000000 Metadata-Version: 1.1
Name: pyudev
Version: 0.21.0
Summary: A libudev binding
Home-page: http://pyudev.readthedocs.org/
Author: Sebastian Wiesner
Author-email: lunaryorn@gmail.com
License: LGPL 2.1+
Description: ######
pyudev
######
.. image:: https://secure.travis-ci.org/pyudev/pyudev.png?branch=develop
:target: http://travis-ci.org/pyudev/pyudev
http://pyudev.readthedocs.org
pyudev is a LGPL_ licensed, pure Python_ binding for libudev_, the device and
hardware management and information library for Linux. It supports almost all
libudev_ functionality. You can enumerate devices, query device properties and
attributes or monitor devices, including asynchronous monitoring with threads,
or within the event loops of Qt, Glib or wxPython.
The binding supports CPython_ 2 (2.6 or newer) and 3 (3.1 or newer), and PyPy_
1.5 or newer. It is tested against udev 151 or newer, earlier versions of udev
as found on dated Linux systems may work, but are not officially supported.
Usage
-----
Usage of pyudev is quite simply thanks to the power of the underlying udev
library. Getting the labels of all partitions just takes a few lines:
>>> import pyudev
>>> context = pyudev.Context()
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print(device.get('ID_FS_LABEL', 'unlabeled partition'))
...
boot
swap
system
The website_ provides a detailed `user guide`_ and a complete `API reference`_.
Support
-------
Please report issues and questions to the issue tracker, but respect the
following guidelines:
- Check that the issue has not already been reported.
- Check that the issue is not already fixed in the ``master`` branch.
- Open issues with clear title and a detailed description in grammatically
correct, complete sentences.
- Include the Python version and the udev version (see ``udevadm --version``) in
the description of your issue.
Development
-----------
The source code is hosted on GitHub_::
git clone git://github.com/pyudev/pyudev.git
Please fork the repository and send pull requests with your fixes or new
features, but respect the following guidelines:
- Read `how to properly contribute to open source projects on GitHub
`_.
- Understand the `branching model
`_.
- Use a topic branch based on the ``develop`` branch to easily amend a pull
request later, if necessary.
- Write `good commit messages
`_.
- Squash commits on the topic branch before opening a pull request.
- Respect :pep:`8` (use pep8_ to check your coding style compliance).
- Add unit tests if possible (refer to the `testsuite documentation
`_).
- Add API documentation in docstrings.
- Open a `pull request `_
that relates to but one subject with a clear title and description in
grammatically correct, complete sentences.
.. _LGPL: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
.. _Python: http://www.python.org/
.. _CPython: http://www.python.org/
.. _PyPy: http://www.pypy.org/
.. _libudev: http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
.. _website: http://pyudev.readthedocs.org
.. _user guide: http://pyudev.readthedocs.org/en/latest/guide.html
.. _api reference: http://pyudev.readthedocs.org/en/latest/api/index.html
.. _issue tracker: http://github.com/lunaryorn/pyudev/issues
.. _GitHub: http://github.com/lunaryorn/pyudev
.. _git: http://www.git-scm.com/
.. _pep8: http://pypi.python.org/pypi/pep8/
Platform: Linux
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Operating System Kernels :: Linux
pyudev-0.21.0/src/pyudev.egg-info/requires.txt 0000664 0001750 0001750 00000000004 12744231230 022443 0 ustar mulhern mulhern 0000000 0000000 six
pyudev-0.21.0/src/pyudev.egg-info/SOURCES.txt 0000664 0001750 0001750 00000003774 12744231230 021750 0 ustar mulhern mulhern 0000000 0000000 CHANGES.rst
COPYING
MANIFEST.in
README.rst
requirements.txt
setup.cfg
setup.py
tox.ini
.tox/py27/lib/python2.7/site-packages/pbr/tests/testpackage/test-requirements.txt
.tox/py27/lib64/python2.7/site-packages/pbr/tests/testpackage/test-requirements.txt
doc/changes.rst
doc/conf.py
doc/contribute.rst
doc/endorsements.rst
doc/guide.rst
doc/index.rst
doc/install.rst
doc/licencing.rst
doc/_templates/info.html
doc/api/index.rst
doc/api/pyudev.glib.rst
doc/api/pyudev.pyqt4.rst
doc/api/pyudev.pyqt5.rst
doc/api/pyudev.pyside.rst
doc/api/pyudev.rst
doc/api/pyudev.wx.rst
doc/tests/index.rst
doc/tests/plugins.rst
doc/tests/running.rst
src/pyudev/__init__.py
src/pyudev/_compat.py
src/pyudev/_qt_base.py
src/pyudev/_util.py
src/pyudev/core.py
src/pyudev/discover.py
src/pyudev/glib.py
src/pyudev/monitor.py
src/pyudev/pyqt4.py
src/pyudev/pyqt5.py
src/pyudev/pyside.py
src/pyudev/version.py
src/pyudev/wx.py
src/pyudev.egg-info/PKG-INFO
src/pyudev.egg-info/SOURCES.txt
src/pyudev.egg-info/dependency_links.txt
src/pyudev.egg-info/requires.txt
src/pyudev.egg-info/top_level.txt
src/pyudev/_ctypeslib/__init__.py
src/pyudev/_ctypeslib/_errorcheckers.py
src/pyudev/_ctypeslib/libc.py
src/pyudev/_ctypeslib/libudev.py
src/pyudev/_ctypeslib/utils.py
src/pyudev/_os/__init__.py
src/pyudev/_os/pipe.py
src/pyudev/_os/poll.py
src/pyudev/device/__init__.py
src/pyudev/device/_device.py
src/pyudev/device/_errors.py
tests/__init__.py
tests/_constants.py
tests/conftest.py
tests/test_core.py
tests/test_device.py
tests/test_discover.py
tests/test_enumerate.py
tests/test_monitor.py
tests/test_observer.py
tests/test_observer_deprecated.py
tests/test_pypi.py
tests/test_util.py
tests/_device_tests/__init__.py
tests/_device_tests/_attributes_tests.py
tests/_device_tests/_device_tests.py
tests/_device_tests/_devices_tests.py
tests/_device_tests/_tags_tests.py
tests/plugins/__init__.py
tests/plugins/fake_monitor.py
tests/plugins/mock_libudev.py
tests/plugins/privileged.py
tests/plugins/travis.py
tests/utils/__init__.py
tests/utils/misc.py
tests/utils/udev.py pyudev-0.21.0/src/pyudev.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 12744231230 024117 0 ustar mulhern mulhern 0000000 0000000
pyudev-0.21.0/src/pyudev.egg-info/top_level.txt 0000664 0001750 0001750 00000000007 12744231230 022600 0 ustar mulhern mulhern 0000000 0000000 pyudev
pyudev-0.21.0/tox.ini 0000664 0001750 0001750 00000001307 12744230565 015602 0 ustar mulhern mulhern 0000000 0000000 [tox]
envlist = py27,py34,doc
[testenv]
setenv=LD_LIBRARY_PATH={envdir}/lib
deps=
docutils>=0.9
pytest>=2.8
mock>=1.0b1
coverage
hypothesis
commands=
py.test {posargs:--junitxml={envname}-tests.xml -rsx}
coverage run --timid --branch -m py.test {posargs:--junitxml={envname}-tests.xml}
coverage report --include="{envsitepackagesdir}/pyudev/*"
coverage html --include="{envsitepackagesdir}/pyudev/*"
[testenv:doc]
downloadcache={toxworkdir}/_download
deps=
mock>=1.0b1
pytest>=2.8
sphinx>=1.0.7
commands=
sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees doc {envtmpdir}/linkcheck
sphinx-build -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html
pyudev-0.21.0/setup.py 0000664 0001750 0001750 00000004354 12660444030 015775 0 ustar mulhern mulhern 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import sys
import setuptools
if sys.version_info[0] < 3:
from codecs import open
def local_file(name):
return os.path.relpath(os.path.join(os.path.dirname(__file__), name))
README = local_file("README.rst")
with open(local_file("src/pyudev/version.py")) as o:
exec(o.read())
setuptools.setup(
name='pyudev',
version=__version__,
url='http://pyudev.readthedocs.org/',
author='Sebastian Wiesner',
author_email='lunaryorn@gmail.com',
description='A libudev binding',
long_description=open(README, encoding='utf-8').read(),
platforms=['Linux'],
license='LGPL 2.1+',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries',
'Topic :: System :: Hardware',
'Topic :: System :: Operating System Kernels :: Linux',
],
install_requires = [
'six'
],
package_dir={"": "src"},
packages=setuptools.find_packages("src"),
)
pyudev-0.21.0/.tox/ 0000775 0001750 0001750 00000000000 12744231232 015146 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/ 0000775 0001750 0001750 00000000000 12744231232 015747 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/ 0000775 0001750 0001750 00000000000 12744231232 016515 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/ 0000775 0001750 0001750 00000000000 12744231232 020265 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/ 0000775 0001750 0001750 00000000000 12744231232 023005 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/pbr/ 0000775 0001750 0001750 00000000000 12744231232 023570 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/pbr/tests/ 0000775 0001750 0001750 00000000000 12744231232 024732 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/pbr/tests/testpackage/ 0000775 0001750 0001750 00000000000 12744231232 027225 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/pbr/tests/testpackage/test-requirements.txt 0000664 0001750 0001750 00000000042 12743701253 033466 0 ustar mulhern mulhern 0000000 0000000 ordereddict;python_version=='2.6'
pyudev-0.21.0/.tox/py27/lib64/ 0000775 0001750 0001750 00000000000 12744231232 016667 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/ 0000775 0001750 0001750 00000000000 12744231232 020437 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/site-packages/ 0000775 0001750 0001750 00000000000 12744231232 023157 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/site-packages/pbr/ 0000775 0001750 0001750 00000000000 12744231232 023742 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/site-packages/pbr/tests/ 0000775 0001750 0001750 00000000000 12744231232 025104 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/site-packages/pbr/tests/testpackage/ 0000775 0001750 0001750 00000000000 12744231232 027377 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/.tox/py27/lib64/python2.7/site-packages/pbr/tests/testpackage/test-requirements.txt 0000664 0001750 0001750 00000000000 12743701253 055071 1pyudev-0.21.0/.tox/py27/lib/python2.7/site-packages/pbr/tests/testpackage/test-requirements.txt ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/MANIFEST.in 0000664 0001750 0001750 00000000451 12744230565 016024 0 ustar mulhern mulhern 0000000 0000000 recursive-include src/pyudev *.py
recursive-include doc *.rst *.py *.html
recursive-include tests *.py
recursive-exclude tests/.hypothesis *.py
recursive-include reproducers *.c *.py
include tox.ini
global-include requirements.txt
include CHANGES.rst COPYING README.rst
include setup.py setup.cfg
pyudev-0.21.0/CHANGES.rst 0000664 0001750 0001750 00000024646 12744230565 016104 0 ustar mulhern mulhern 0000000 0000000 0.21.0 (July 20, 2016)
======================
- Deprecate use of Device object as mapping from udev property names to values.
- Add a Properties class and a Device.properties() method for udev properties.
- Fix places where Device object was incorrectly used in a boolean context.
- Return an empty string, not None, if the property value is an empty string.
- Exceptions re-raised from libudev calls now have a message string.
- Insert a warning about using a Device in a boolean context in docs.
- Infrastructure for vagrant tests is removed.
- Various internal refactorings.
- Extensive test improvements.
- Numerous documentation fixes.
0.20.0 (April 29, 2016)
=======================
- Remove parsing code added in previous release.
- No longer do CI for Python 2.6.
- Eliminate all wildcard imports and __all__ statements.
- No longer use deprecated Device.from_sys_path() method.
- Minor pylint induced changes.
- Documentation fixes.
0.19.0 (Feb 3, 2016)
==================
- Restore raising KeyError by Attributes.as* methods when attribute not found.
- Explicitly require six module.
- Never raise a DeviceNotFoundError when iterating over a device enumeration.
- Device.subsystem() now returns None if device has no subsystem.
- Add DeprecationWarnings to deprecated Device methods.
- Replace "/" with "!" in Device.from_name() sys_name parameter.
- Add some unstable classes for parsing some kinds of values.
- Make version info more like Python's including micro numbers and levels.
- Refactor some internal modules into subdirectories.
- Work on tests and reproducers.
0.18 (Dec 1, 2015)
===================
- DeviceNotFoundError is no longer a subtype of LookupError
- Added support for pyqt5 monitor observer
- Added discover module, which looks up a device on limited information
- Attributes class no longer extends Mapping, extends object instead
- Attributes class no longer inherits [] operator, Mapping methods
- Attributes class objects are no longer iterable
- Attributes.available_attributes property added
- Attributes.get() method, with usual semantics, defined
- Device.from_* methods are deprecated, uses Devices.from_* methods instead
- Device.from_device_file() now raises DeviceNotFoundByFileError
- Device.from_device_number() now raises DeviceNotFoundByNumberError
- Devices.from_interface_index() method added
- Devices.from_kernel_device() method added
- Numerous testing infrastructure changes
0.17 (Aug 26, 2015)
=====================
- #52: Remove global libudev object
- #57: Really start the monitor on :meth:`pyudev.Monitor.poll()`
- #60: Do not use :meth:`select.select` to avoid hitting its file descriptor
limit
- #58: Force non-blocking IO in :class:`pyudev.Monitor` to avoid blocking on
receiving the device
- #63: Set proper flags on pipe fds.
- #65: Handle irregular polling events properly.
- #50: Add :class:`pyudev.wx.MonitorObserver` and deprecate
:class:`pyudev.wx.WxUDevMonitorObserver`
- #50: Add :class:`pyudev.glib.MonitorObserver` and deprecate
:class:`pyudev.glib.GUDevMonitorObserver`
- #50: Add :class:`pyudev.pyqt4.MonitorObserver` and deprecate
:class:`pyudev.pyqt4.QUDevMonitorObserver`
- #50: Add :class:`pyudev.pyside.MonitorObserver` and deprecate
:class:`pyudev.pyside.QUDevMonitorObserver`
- Add a wrapper function to retry interruptible system calls.
0.16.1 (Aug 02, 2012)
=====================
- #53: Fix source distribution
- #54: Fix typo in test
0.16 (Jul 25, 2012)
===================
- Remove :meth:`pyudev.Monitor.from_socket`.
- Deprecate :meth:`pyudev.Device.traverse()` in favor of
:attr:`pyudev.Device.ancestors`.
- #47: Deprecate :meth:`pyudev.Monitor.receive_device` in favor of
:attr:`pyudev.Monitor.poll`.
- #47: Deprecate :attr:`pyudev.Monitor.enable_receiving` in favor of
:attr:`pyudev.Monitor.start`.
- #47: Deprecate :attr:`pyudev.Monitor.__iter__` in favor of explicit looping or
:class:`pyudev.MonitorObserver`.
- #49: Deprecate ``event_handler`` to :class:`pyudev.MonitorObserver` in favour
of ``callback`` argument.
- #46: Continuously test pyudev on Travis-CI.
- Add :attr:`pyudev.Device.ancestors`.
- Add :attr:`pyudev.Device.action`.
- #10: Add :attr:`pyudev.Device.sequence_number`.
- #47: Add :meth:`pyudev.Monitor.poll`.
- #47: Add :attr:`pyudev.Monitor.started`.
- #49: Add ``callback`` argument to :class:`pyudev.Monitor`.
- :meth:`pyudev.Monitor.start` can be called repeatedly.
- #45: Get rid of 2to3
- #43: Fix test failures on Python 2.6
- Fix signature in declaration of ``udev_monitor_set_receive_buffer_size``.
- #44: Test wrapped signatures with help of ``gccxml``.
- Fix compatibility with udev 183 and newer in :class:`pyudev.Context`.
- :meth:`pyudev.MonitorObserver.stop` can be called from the observer thread.
0.15 (Mar 1, 2012)
==================
- #20: Add :meth:`~pyudev.Monitor.remove_filter()`.
- #40: Add user guide to the documentation.
- #39: Add :meth:`pyudev.Device.from_device_file()`.
- :data:`errno.EINVAL` from underlying libudev functions causes
:exc:`~exceptions.ValueError` instead of :exc:`~exceptions.EnvironmentError`.
- :class:`pyudev.MonitorObserver` calls
:meth:`pyudev.Monitor.enable_receiving()` when started.
- #20: :meth:`pyudev.Monitor.filter_by()` and
:meth:`pyudev.Monitor.filter_by_tag()` can be called after
:meth:`pyudev.Monitor.enable_receiving()`.
0.14 (Feb 10, 2012)
===================
- Host documentation at http://pyudev.readthedocs.org (thanks to the
readthedocs.org team for this service)
- #37: Add :class:`pyudev.wx.WxUDevMonitorObserver` for wxPython (thanks to
Tobias Eberle).
- Add :class:`pyudev.MonitorObserver`.
- Add :attr:`pyudev.glib.GUDevMonitorObserver.enabled`,
:attr:`pyudev.pyqt4.QUDevMonitorObserver.enabled` and
:attr:`pyudev.pyside.QUDevMonitorObserver.enabled`.
0.13 (Nov 4, 2011)
==================
- #36: Add :meth:`pyudev.Monitor.set_receive_buffer_size` (thanks to Rémi
Rérolle).
- Add :meth:`pyudev.Enumerator.match_parent`.
- Add ``parent`` keyword argument to :meth:`pyudev.Enumerator.match()`.
- #31: Add :meth:`pyudev.Enumerator.match_attribute`.
- Add ``nomatch`` argument to :meth:`pyudev.Enumerator.match_subsystem` and
:meth:`pyudev.Enumerator.match_attribute`.
- Remove :meth:`pyudev.Enumerator.match_children` in favour of
:meth:`pyudev.Enumerator.match_parent`.
- #34: :class:`pyudev.Device.tags` returns a :class:`pyudev.Tags` object.
- :attr:`pyudev.Device.children` requires udev version 172 now
0.12 (Aug 31, 2011)
===================
- #32: Fix memory leak.
- #33: Fix Python 3 support for :mod:`pyudev.glib`.
- Fix license header in :mod:`pyudev._compat`.
0.11 (Jun 26, 2011)
===================
- #30: Add :attr:`pyudev.Device.sys_number`.
- #29: Add :meth:`pyudev.Device.from_device_number`
- #29: Add :attr:`pyudev.Device.device_number`.
- Support PyPy.
0.10 (Apr 20, 2011)
===================
- Add :attr:`pyudev.__version_info__`
- Add :attr:`pyudev.Device.device_type`
- :class:`pyudev.Context`, :class:`pyudev.Enumerator`, :class:`pyudev.Device`
and :class:`pyudev.Monitor` can directly be passed to
:mod:`ctypes`-wrapped functions.
- #24: Add :attr:`pyudev.Context.run_path`.
0.9 (Mar 09, 2011)
==================
- #21: Add :meth:`pyudev.Device.find_parent`.
- #22: Add :meth:`pyudev.Monitor.filter_by_tag`.
- Add :attr:`pyudev.Context.log_priority`.
- Improve error reporting, if libudev is missing.
0.8 (Jan 08, 2011)
==================
- #16: Add :meth:`pyudev.Enumerator.match`.
- Add keyword arguments to :meth:`pyudev.Context.list_devices()`.
- #19: Add :meth:`pyudev.Enumerator.match_sys_name`.
- #18: Add :func:`pyudev.udev_version()`.
- #17: Add :attr:`pyudev.Device.is_initialized`.
- #17: Add :attr:`pyudev.Device.time_since_initialized`.
- #17: Add :meth:`pyudev.Enumerator.match_is_initialized`
- Fix support for earlier releases of udev.
- Document minimum udev version for all affected attributes.
0.7 (Nov 15, 2010)
==================
- #15: Add :mod:`pyudev.glib.GUDevMonitorObserver`.
0.6 (Oct 03, 2010)
==================
- #8: Add :attr:`pyudev.Device.tags`.
- #8: Add :meth:`pyudev.Enumerator.match_tag`.
- #11: Add :meth:`pyudev.Device.from_environment`
- #5: Add :mod:`pyudev.pyside`
- #14: Remove apipkg_ dependency.
- #14: Require explicit import of :mod:`pyudev.pyqt4`.
- Fix licence headers in source files.
.. _apipkg: http://pypi.python.org/pypi/apipkg/
0.5 (Sep 06, 2010)
==================
- Support Python 3.
- #6: Add :attr:`pyudev.Device.attributes` (thanks to Daniel Lazzari).
- #6: Add :class:`pyudev.Attributes` (thanks to Daniel Lazzari).
- #7: :attr:`pyudev.Device.context` and :attr:`pyudev.Monitor.context` are
part of the public API.
- #9: Add :attr:`pyudev.Device.driver`.
- #12: Add :meth:`pyudev.Device.from_name`.
- Rename :exc:`pyudev.NoSuchDeviceError` to :exc:`pyudev.DeviceNotFoundError`.
- :meth:`pyudev.Device.from_sys_path` raises
:exc:`pyudev.DeviceNotFoundAtPathError`.
- #13: Fix :exc:`~exceptions.AttributeError` in
:attr:`pyudev.Device.device_node`.
- Improve and extend documentation.
- Add more tests.
0.4 (Aug 23, 2010)
==================
API changes
-----------
- #3: Rename :mod:`udev` to :mod:`pyudev`.
- #3: Rename :mod:`qudev` to :mod:`pyudev.pyqt4`.
- Add :meth:`pyudev.Device.from_path`.
- :meth:`pyudev.Device.from_sys_path` raises :exc:`pyudev.NoSuchDeviceError`.
- :meth:`pyudev.Monitor.receive_device` raises
:exc:`~exceptions.EnvironmentError`.
- ``errno``, ``strerror`` and ``filename`` attributes of
:class:`~exceptions.EnvironmentError` exceptions have meaningful content.
- Fix :exc:`~exceptions.NameError` in :meth:`pyudev.Monitor.from_socket`
- ``subsystem`` argument to :meth:`pyudev.Monitor.filter_by` is mandatory.
- Delete underlying C objects if :class:`pyudev.Device` is garbage-collected.
- Fix broken signal emitting in :class:`pyudev.pyqt4.QUDevMonitorObserver`.
0.3 (Jul 28, 2010)
==================
- #1: Fix documentation to reflect the actual behaviour of the underlying
API
- Raise :exc:`~exceptions.TypeError` if :class:`udev.Device` are compared with
``>``, ``>=``, ``<`` or ``<=``.
- Add :meth:`udev.Enumerator.match_children`.
- Add :attr:`udev.Device.children`.
- Add :meth:`qudev.QUDevMonitorObserver.deviceChanged`.
- Add :meth:`qudev.QUDevMonitorObserver.deviceMoved`.
0.2 (Jun 28, 2010)
==================
- Add :class:`udev.Monitor`.
- Add :meth:`udev.Device.asbool`.
- Add :meth:`udev.Device.asint`.
- Remove type magic in :meth:`udev.Device.__getitem__`.
- Add :mod:`qudev`.
0.1 (May 03, 2010)
==================
- Initial release.
- Add :class:`udev.Context`.
- Add :class:`udev.Device`.
- Add :class:`udev.Enumerator`.
pyudev-0.21.0/doc/ 0000775 0001750 0001750 00000000000 12744231232 015023 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/doc/tests/ 0000775 0001750 0001750 00000000000 12744231232 016165 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/doc/tests/running.rst 0000664 0001750 0001750 00000002330 12744230565 020405 0 ustar mulhern mulhern 0000000 0000000 Test running
============
Direct testing using tox_
-------------------------
If you are on a Linux system run all tests with tox_. This tool automatically
creates virtualenvs (see virtualenv_), installs all packages required by the
test suite, and runs the tests.
Run all pyudev tests against Python 2.7, Python 3.2 and PyPy::
tox -e py27,py32,pypy
Pass any arguments you want to :program:`py.test` after two dashes ``--``::
tox -e py27,py32,pypy -- --enable-privileged
Notes
-----
Device samples
~~~~~~~~~~~~~~
Many pyudev tests run against the real device database of the system the tests
are executed on. As testing against the whole database takes a long time,
tests are run against a random sample by default. With the command line
options provided by :mod:`~tests.plugins.udev_database` you can configure the
size of this sample, or run the tests against a single device or the whole
database.
Privileged tests
~~~~~~~~~~~~~~~~
Some tests need to execute privileged operations like loading or unloading of
kernel modules to trigger real udev events. These tests are disabled by
default. Refer to :mod:`~tests.plugins.privileged` for more information on how
to enable these tests and configure them properly.
pyudev-0.21.0/doc/tests/plugins.rst 0000664 0001750 0001750 00000005656 12654153421 020417 0 ustar mulhern mulhern 0000000 0000000 :mod:`plugins` – Testsuite plugins
==================================
.. automodule:: plugins
The following plugins are provided and enabled:
.. autosummary::
.privileged
.fake_monitor
.mock_libudev
.travis
:mod:`~plugins.privileged` – Privileged operations
--------------------------------------------------
.. automodule:: plugins.privileged
Command line options
~~~~~~~~~~~~~~~~~~~~
The plugin adds the following command line options to :program:`py.test`:
.. program:: py.test
.. option:: --enable-privileged
Enable privileged tests. You'll need to have :program:`sudo` configured
correctly in order to run tests with this option.
Configuration
~~~~~~~~~~~~~
In order to execute these tests without failure, you need to configure :program:`sudo`
to allow the user that executes the test to run the following commands:
- ``modprobe dummy``
- ``modprobe -r dummy``
To do so, create a file ``/etc/sudoers.d/20pyudev-tests`` with the following
content::
me ALL = (root) NOPASSWD: /sbin/modprobe dummy, /sbin/modprobe -r dummy
Replace ``me`` with your actual user name. ``NOPASSWD:`` tells :program:`sudo`
not to ask for a password when executing these commands. This is simply for
the sake of convenience and to allow unattended test execution. Remove this
word if you want to be asked for a password.
Make sure to change the owner and group to ``root:root`` and the permissions of
this file to ``440`` afterwards, other :program:`sudo` will refuse to load the
file. Also check the file with :program:`visudo` to prevent syntactic errors::
$ chown root:root /etc/sudoers.d/20pyudev-tests
$ chmod 440 /etc/sudoers.d/20pyudev-tests
$ visudo -c -f /etc/sudoers.d/20pyudev-tests
:mod:`pytest` namespace
~~~~~~~~~~~~~~~~~~~~~~~
The plugin adds the following functions to the :mod:`pytest` namespace:
.. autofunction:: load_dummy
.. autofunction:: unload_dummy
:mod:`~plugins.fake_monitor` – A fake :class:`Monitor`
------------------------------------------------------
.. automodule:: plugins.fake_monitor
.. autoclass:: FakeMonitor
:members:
Funcargs
~~~~~~~~
The plugin provides the following :ref:`funcargs `:
.. autofunction:: fake_monitor
:mod:`~plugins.mock_libudev` – Mock calls to libudev
----------------------------------------------------
.. automodule:: plugins.mock_libudev
.. autofunction:: libudev_list(function, items)
:mod:`~plugins.travis` – Support for Travis CI
----------------------------------------------
.. automodule:: plugins.travis
Test markers
~~~~~~~~~~~~
.. attribute:: pytest.mark.not_on_travis
Do not run the decorated test on Travis CI::
@pytest.mark.not_on_travis
def test_foo():
assert True
``test_foo`` will not be run on Travis CI.
:mod:`pytest` namespace
~~~~~~~~~~~~~~~~~~~~~~~
The plugin adds the following functions to the :mod:`pytest` namespace:
.. autofunction:: is_on_travis_ci
.. _pytest: http://pytest.org
pyudev-0.21.0/doc/tests/index.rst 0000664 0001750 0001750 00000000705 12654153421 020033 0 ustar mulhern mulhern 0000000 0000000 Testsuite documentation
=======================
This document explains the pyudev test suite and how to add new tests to this
suite.
The pyudev testsuite uses the powerful pytest_ unittest framework, accompied by
the nice mock_ library for mocking native functions and heavily extended with
plugins to support the tests.
.. toctree::
running.rst
plugins.rst
.. _pytest: http://pytest.org
.. _mock: http://www.voidspace.org.uk/python/mock/
pyudev-0.21.0/doc/changes.rst 0000664 0001750 0001750 00000000061 12654153421 017165 0 ustar mulhern mulhern 0000000 0000000 Changelog
*********
.. include:: ../CHANGES.rst
pyudev-0.21.0/doc/conf.py 0000664 0001750 0001750 00000010430 12660444030 016317 0 ustar mulhern mulhern 0000000 0000000 # -*- coding: utf-8 -*-
# Copyright (C) 2010, 2011, 2012, 2013 Sebastian Wiesner
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version.
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import sys
import os
from docutils import nodes
from docutils.parsers.rst import Directive
# add the pyudev source directory to our path
doc_directory = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.normpath(
os.path.join(doc_directory, os.pardir)))
# add the tests directory to our path to point autodoc on the testsuite plugins
sys.path.append(os.path.normpath(
os.path.join(doc_directory, os.pardir, 'tests')))
class Mock(object):
"""
Mock modules.
Taken from
http://read-the-docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
with some slight changes.
"""
@classmethod
def mock_modules(cls, *modules):
for module in modules:
sys.modules[module] = cls()
def __init__(self, *args, **kwargs):
pass
def __call__(self, *args, **kwargs):
return self.__class__()
def __getattr__(self, attribute):
if attribute in ('__file__', '__path__'):
return os.devnull
else:
# return the *class* object here. Mocked attributes may be used as
# base class in pyudev code, thus the returned mock object must
# behave as class, or else Sphinx autodoc will fail to recognize
# the mocked base class as such, and "autoclass" will become
# meaningless
return self.__class__
# mock out native modules used throughout pyudev to enable Sphinx autodoc even
# if these modules are unavailable, as on readthedocs.org
Mock.mock_modules('PyQt5', 'PyQt5.QtCore',
'PyQt4', 'PyQt4.QtCore',
'PySide', 'PySide.QtCore',
'glib', 'gobject',
'wx', 'wx.lib', 'wx.lib.newevent',
'pyudev._libudev')
# mock out the NewEvent function of wxPython. Let's praise the silly wx API
def NewEventMock():
yield 'event_class'
yield 'event_constant'
sys.modules['wx.lib.newevent'].NewEvent = NewEventMock
import pyudev
needs_sphinx = '1.0'
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary',
'sphinx.ext.intersphinx']
master_doc = 'index'
exclude_patterns = ['_build/*']
source_suffix = '.rst'
project = u'pyudev'
copyright = u'2010, 2011 Sebastian Wiesner'
version = '.'.join(pyudev.__version__.split('.')[:2])
release = pyudev.__version__
templates_path = ['_templates']
html_theme = 'classic'
html_static_path = []
html_sidebars = {'**': ['info.html', 'localtoc.html', 'relations.html',
'sourcelink.html']}
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
'pytest': ('http://pytest.org/latest', None)}
class UDevVersion(Directive):
"""
Directive to document the minimum udev version to use an attribute or
method
"""
has_content = False
required_arguments = 1
option_spec = {}
def run(self):
udevversion = self.arguments[0]
para = nodes.paragraph(udevversion, '', classes=['udevversion'])
text = 'Required udev version: {0}'.format(*self.arguments)
node = nodes.inline(udevversion, text, classes=['versionmodified'])
para.append(node)
return [para]
def setup(app):
from sphinx.ext.autodoc import cut_lines
app.connect(b'autodoc-process-docstring', cut_lines(2, what=['module']))
app.add_directive('udevversion', UDevVersion)
pyudev-0.21.0/doc/api/ 0000775 0001750 0001750 00000000000 12744231232 015574 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/doc/api/pyudev.pyside.rst 0000664 0001750 0001750 00000004000 12654153421 021133 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev.pyside` – PySide_ integration
==========================================
.. automodule:: pyudev.pyside
:platform: Linux
:synopsis: PySide integration
.. autoclass:: MonitorObserver
.. automethod:: __init__
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: notifier
The underlying :class:`QtCore.QSocketNotifier` used to watch the
:attr:`monitor`.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following Qt signal:
.. method:: deviceEvent(device)
Emitted upon any device event.
``device`` is the :class:`~pyudev.Device` object describing the device.
Use :attr:`~pyudev.Device.action` to get the type of event.
Deprecated API
--------------
.. autoclass:: QUDevMonitorObserver
.. automethod:: __init__
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: notifier
The underlying :class:`QtCore.QSocketNotifier` used to watch the
:attr:`monitor`.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following Qt signals:
.. method:: deviceEvent(action, device)
Emitted upon any device event. ``action`` is a unicode string
containing the action name, and ``device`` is the
:class:`~pyudev.Device` object describing the device.
Basically the arguments of this signal are simply the return value of
:meth:`~pyudev.Monitor.receive_device`
.. method:: deviceAdded(device)
Emitted if a :class:`~pyudev.Device` is added (e.g a USB device was
plugged).
.. method:: deviceRemoved(device)
Emitted if a :class:`~pyudev.Device` is removed (e.g. a USB device was
unplugged).
.. method:: deviceChanged(device)
Emitted if a :class:`~pyudev.Device` was somehow changed (e.g. a
change of a property)
.. method:: deviceMoved(device)
Emitted if a :class:`~pyudev.Device` was renamed, moved or
re-parented.
pyudev-0.21.0/doc/api/index.rst 0000664 0001750 0001750 00000000434 12660444030 017435 0 ustar mulhern mulhern 0000000 0000000 API documentation
=================
This document provides API reference documentation for pyudev. Refer to the
:doc:`../guide` for an introduction into pyudev.
.. autosummary::
:toctree: .
pyudev
pyudev.pyqt4
pyudev.pyqt5
pyudev.pyside
pyudev.glib
pyudev.wx
pyudev-0.21.0/doc/api/pyudev.rst 0000664 0001750 0001750 00000012617 12654153421 017654 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev` - libudev binding
===============================
.. automodule:: pyudev
:platform: Linux
:synopsis: libudev bindings
.. autosummary::
:nosignatures:
Context
Device
Devices
Monitor
MonitorObserver
Version information
-------------------
.. data:: __version__
The version of :mod:`pyudev` as string. This string contains a major and a
minor version number, and optionally a revision in the form
``major.minor.revision``. As said, the ``revision`` part is optional and
may not be present.
This attribute is mainly intended for display purposes, use
:data:`__version_info__` to check the version of :mod:`pyudev` in source
code.
.. data:: __version_info__
The version of :mod:`pyudev` as tuple of integers. This tuple contains a
major and a minor number, and optionally a revision number in the form
``(major, minor, revision)``. As said, the ``revision`` component is
optional and may not be present.
.. versionadded:: 0.10
.. autofunction:: udev_version()
:class:`Context` – UDev database context
----------------------------------------
.. autoclass:: Context
.. automethod:: __init__
.. autoattribute:: sys_path
.. autoattribute:: device_path
.. autoattribute:: run_path
.. autoattribute:: log_priority
.. automethod:: list_devices
:class:`Enumerator` – device enumeration and filtering
------------------------------------------------------
.. autoclass:: Enumerator()
.. automethod:: match
.. automethod:: match_subsystem
.. automethod:: match_sys_name
.. automethod:: match_property
.. automethod:: match_attribute
.. automethod:: match_tag
.. automethod:: match_parent
.. automethod:: match_is_initialized
.. automethod:: __iter__
:class:`Devices` – constructing `Device` objects
------------------------------------------------
.. autoclass:: Devices()
.. rubric:: Construction of device objects
.. automethod:: from_path
.. automethod:: from_sys_path
.. automethod:: from_name
.. automethod:: from_device_number
.. automethod:: from_device_file
.. automethod:: from_environment
.. automethod:: METHODS
:class:`Device` – accessing device information
----------------------------------------------
.. autoclass:: Device()
.. rubric:: Construction of device objects
.. automethod:: from_path
.. automethod:: from_sys_path
.. automethod:: from_name
.. automethod:: from_device_number
.. automethod:: from_device_file
.. automethod:: from_environment
.. rubric:: General attributes
.. attribute:: context
The :class:`Context` to which this device is bound.
.. versionadded:: 0.5
.. autoattribute:: sys_path
.. autoattribute:: sys_name
.. autoattribute:: sys_number
.. autoattribute:: device_path
.. autoattribute:: tags
.. rubric:: Device driver and subsystem
.. autoattribute:: subsystem
.. autoattribute:: driver
.. autoattribute:: device_type
.. rubric:: Device nodes
.. autoattribute:: device_node
.. autoattribute:: device_number
.. autoattribute:: device_links
.. rubric:: Device initialization time
.. autoattribute:: is_initialized
.. autoattribute:: time_since_initialized
.. rubric:: Device hierarchy
.. autoattribute:: parent
.. autoattribute:: ancestors
.. autoattribute:: children
.. automethod:: find_parent
.. rubric:: Device events
.. autoattribute:: action
.. autoattribute:: sequence_number
.. rubric:: Device properties
.. automethod:: __iter__
.. automethod:: __len__
.. automethod:: __getitem__
.. automethod:: asint
.. automethod:: asbool
.. rubric:: Sysfs attributes
.. autoattribute:: attributes
.. rubric:: Deprecated members
.. automethod:: traverse
.. autoclass:: Attributes()
.. attribute:: device
The :class:`Device` to which these attributes belong.
.. automethod:: __iter__
.. automethod:: __len__
.. automethod:: __getitem__
.. automethod:: asstring
.. automethod:: asint
.. automethod:: asbool
.. autoclass:: Tags()
.. automethod:: __iter__
.. automethod:: __contains__
:class:`Device` exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: DeviceNotFoundError
.. autoclass:: DeviceNotFoundAtPathError
:members:
.. autoclass:: DeviceNotFoundByNameError
:members:
.. autoclass:: DeviceNotFoundByNumberError
:members:
.. autoclass:: DeviceNotFoundInEnvironmentError
:members:
:class:`Monitor` – device monitoring
------------------------------------
.. autoclass:: Monitor()
.. automethod:: from_netlink
.. attribute:: context
The :class:`Context` to which this monitor is bound.
.. versionadded:: 0.5
.. autoattribute:: started
.. automethod:: fileno
.. automethod:: filter_by
.. automethod:: filter_by_tag
.. automethod:: remove_filter
.. automethod:: start
.. automethod:: set_receive_buffer_size
.. automethod:: poll
.. rubric:: Deprecated members
.. automethod:: enable_receiving
.. automethod:: receive_device
.. automethod:: __iter__
:class:`MonitorObserver` – asynchronous device monitoring
---------------------------------------------------------
.. autoclass:: MonitorObserver
.. attribute:: monitor
Get the :class:`Monitor` observer by this object.
.. automethod:: __init__
.. automethod:: send_stop
.. automethod:: stop
pyudev-0.21.0/doc/api/pyudev.glib.rst 0000664 0001750 0001750 00000004556 12654153421 020573 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev.glib` – Glib/Gtk 2 integration
===========================================
.. automodule:: pyudev.glib
:platform: Linux
:synopsis: Glib integration
.. autoclass:: MonitorObserver
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: event_source
The event source, which represents the watch on the :attr:`monitor`
(as returned by :func:`glib.io_add_watch`), or ``None``, if
:attr:`enabled` is ``False``.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following GObject signal:
.. method:: device-event(observer, action, device)
Emitted upon any device event.
``observer`` is the :class:`MonitorObserver`, which emitted the
signal. ``device`` is the :class:`~pyudev.Device`, which caused this
event.
Use :attr:`~pyudev.Device.action` to get the type of event.
Deprecated API
--------------
.. autoclass:: GUDevMonitorObserver
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: event_source
The event source, which represents the watch on the :attr:`monitor`
(as returned by :func:`glib.io_add_watch`), or ``None``, if
:attr:`enabled` is ``False``.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following GObject signals:
.. method:: device-event(observer, action, device)
Emitted upon any device event. ``observer`` is the
:class:`GUDevMonitorObserver`, which emitted the signal. ``action``
is a unicode string containing the action name, and ``device`` is the
:class:`~pyudev.Device`, which caused this event.
Basically the last two arguments of this signal are simply the
return value of :meth:`~pyudev.Monitor.receive_device`
.. method:: device-added(observer, device)
Emitted if a :class:`~pyudev.Device` is added (e.g a USB device was
plugged).
.. method:: device-removed(observer, device)
Emitted if a :class:`~pyudev.Device` is removed (e.g. a USB device was
unplugged).
.. method:: device-changed(observer, device)
Emitted if a :class:`~pyudev.Device` was somehow changed (e.g. a
change of a property)
.. method:: device-moved(observer, device)
Emitted if a :class:`~pyudev.Device` was renamed, moved or
re-parented.
pyudev-0.21.0/doc/api/pyudev.wx.rst 0000664 0001750 0001750 00000005024 12654153421 020303 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev.wx` – wxPython_ integration
=====================================================
.. automodule:: pyudev.wx
:platform: Linux
:synopsis: wxWidgets integration
.. autoclass:: MonitorObserver
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. autoattribute:: enabled
.. rubric:: Events
:class:`MonitorObserver` posts the following event:
.. data:: EVT_DEVICE_EVENT
Emitted upon any device event. Receivers get a :class:`DeviceEvent` object
as argument.
.. class:: DeviceEvent
Argument object for :data:`EVT_DEVICE_EVENT`.
.. attribute:: device
The :class:`~pyudev.Device` object that caused this event.
Use :attr:`~pyudev.Device.action` to get the type of event.
.. rubric:: Deprecated members
.. attribute:: action
A unicode string containing the action name.
.. deprecated:: 0.17
Will be removed in 1.0. Use :attr:`~pyudev.Device.action` instead.
Deprecated API
--------------
.. autoclass:: WxUDevMonitorObserver
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. autoattribute:: enabled
.. rubric:: Events
:class:`WxUDevMonitorObserver` posts the following events in addition to
:data:`EVT_DEVICE_EVENT`:
.. data:: EVT_DEVICE_ADDED
Emitted if a :class:`~pyudev.Device` is added (e.g a USB device was
plugged). Receivers get a :class:`DeviceAddedEvent` object as argument.
.. deprecated:: 0.17
Will be removed in 1.0.
.. data:: EVT_DEVICE_REMOVED
Emitted if a :class:`~pyudev.Device` is removed (e.g. a USB device was
unplugged). Receivers get a :class:`DeviceRemovedEvent` object as argument.
.. deprecated:: 0.17
Will be removed in 1.0.
.. data:: EVT_DEVICE_CHANGED
Emitted if a :class:`~pyudev.Device` was somehow changed (e.g. a change of a
property). Receivers get a :class:`DeviceChangedEvent` object as argument.
.. deprecated:: 0.17
Will be removed in 1.0.
.. data:: EVT_DEVICE_MOVED
Emitted if a :class:`~pyudev.Device` was renamed, moved or re-parented.
Receivers get a :class:`DeviceMovedEvent` object as argument.
.. class:: DeviceAddedEvent
DeviceRemovedEvent
DeviceChangedEvent
DeviceMovedEvent
Argument objects for :data:`EVT_DEVICE_ADDED`, :data:`EVT_DEVICE_REMOVED`,
:data:`EVT_DEVICE_CHANGED` and :data:`EVT_DEVICE_MOVED`.
.. deprecated:: 0.17
Will be removed in 1.0.
.. attribute:: device
The :class:`~pyudev.Device` object that caused this event.
pyudev-0.21.0/doc/api/pyudev.pyqt5.rst 0000664 0001750 0001750 00000001471 12654153421 020731 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev.pyqt5` – PyQt5_ integration
========================================
.. automodule:: pyudev.pyqt5
:platform: Linux
:synopsis: PyQt5 integration
.. autoclass:: MonitorObserver
.. automethod:: __init__
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: notifier
The underlying :class:`QtCore.QSocketNotifier` used to watch the
:attr:`monitor`.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following Qt signal:
.. method:: deviceEvent(device)
Emitted upon any device event.
``device`` is the :class:`~pyudev.Device` object describing the device.
Use :attr:`~pyudev.Device.action` to get the type of event.
.. _PyQt5: http://riverbankcomputing.co.uk/software/pyqt/intro
pyudev-0.21.0/doc/api/pyudev.pyqt4.rst 0000664 0001750 0001750 00000003772 12654153421 020736 0 ustar mulhern mulhern 0000000 0000000 :mod:`pyudev.pyqt4` – PyQt4_ integration
========================================
.. automodule:: pyudev.pyqt4
:platform: Linux
:synopsis: PyQt4 integration
.. autoclass:: MonitorObserver
.. automethod:: __init__
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: notifier
The underlying :class:`QtCore.QSocketNotifier` used to watch the
:attr:`monitor`.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following Qt signal:
.. method:: deviceEvent(device)
Emitted upon any device event.
``device`` is the :class:`~pyudev.Device` object describing the device.
Use :attr:`~pyudev.Device.action` to get the type of event.
Deprecated API
--------------
.. autoclass:: QUDevMonitorObserver
.. automethod:: __init__
.. attribute:: monitor
The :class:`~pyudev.Monitor` observed by this object.
.. attribute:: notifier
The underlying :class:`QtCore.QSocketNotifier` used to watch the
:attr:`monitor`.
.. autoattribute:: enabled
.. rubric:: Signals
This class emits the following Qt signals:
.. method:: deviceEvent(action, device)
Emitted upon any device event. ``action`` is a unicode string
containing the action name, and ``device`` is the
:class:`~pyudev.Device` object describing the device.
Basically the arguments of this signal are simply the return value of
:meth:`~pyudev.Monitor.receive_device`
.. method:: deviceAdded(device)
Emitted if a :class:`~pyudev.Device` is added (e.g a USB device was
plugged).
.. method:: deviceRemoved(device)
Emitted if a :class:`~pyudev.Device` is removed (e.g. a USB device was
unplugged).
.. method:: deviceChanged(device)
Emitted if a :class:`~pyudev.Device` was somehow changed (e.g. a
change of a property)
.. method:: deviceMoved(device)
Emitted if a :class:`~pyudev.Device` was renamed, moved or
re-parented.
pyudev-0.21.0/doc/licencing.rst 0000664 0001750 0001750 00000000064 12654153421 017513 0 ustar mulhern mulhern 0000000 0000000 Licencing
=========
.. literalinclude:: ../COPYING
pyudev-0.21.0/doc/endorsements.rst 0000664 0001750 0001750 00000000707 12654153421 020272 0 ustar mulhern mulhern 0000000 0000000 pyudev Users
============
If you are using pyudev and would like the world to know how and why, here is
the place. Just submit a PR with an addition to the documentation, something
like:
--------------------------------------------------------------------------------
Choice of information about yourself.
--------------------------------------------------------------------------------
What you are doing with pyudev and why it beats the alternatives.
pyudev-0.21.0/doc/index.rst 0000664 0001750 0001750 00000005141 12660444030 016664 0 ustar mulhern mulhern 0000000 0000000 pyudev -- pure Python libudev_ binding
======================================
pyudev |release| (:doc:`changes`, :doc:`installation `)
pyudev is a :doc:`LGPL licenced `, pure Python_ 2/3 binding to
libudev_, the device and hardware management and information library of Linux.
Almost the complete libudev_ functionality is exposed. You can:
* Enumerate devices, filtered by specific criteria (:class:`pyudev.Context`)
* Query device information, properties and attributes,
* Monitor devices, both synchronously and asynchronously with background
threads, or within the event loops of Qt (:mod:`pyudev.pyqt4`,
:mod:`pyudev.pyside`), glib (:mod:`pyudev.glib`) and wxPython
(:mod:`pyudev.wx`).
Documentation
-------------
Thanks to the power of libudev_, usage of pyudev is very simple. Getting the
labels of all partitions just takes a few lines:
>>> import pyudev
>>> context = pyudev.Context()
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print(device.get('ID_FS_LABEL', 'unlabeled partition'))
...
boot
swap
system
A user guide gives an introduction into common operations and concepts of
pyudev, the API documentation provides a detailed reference:
.. toctree::
:maxdepth: 2
install
guide
api/index
Support
-------
Please report issues, bugs and questions to the `issue tracker`_, but respect
the following guidelines:
- Check that the issue has not already been reported.
- Check that the issue is not already fixed in the ``master`` branch.
- Open issues with clear title and a detailed description in grammatically
correct, complete sentences.
- Include the Python version and the udev version (see ``udevadm --version``) in
the description of your issue.
.. _development:
Development
-----------
The source code is hosted on GitHub_::
git clone https://github.com/lunaryorn/pyudev.git
If you want to contribute to pyudev, please read the guidelines for
contributions and the testsuite documentation.
.. toctree::
:maxdepth: 2
contribute
tests/index
Endorsements
------------
If you're using pyudev and want to say something about it please add yourself
to the endorsements page.
.. toctree::
:maxdepth: 1
endorsements
Other reading
-------------
.. toctree::
:maxdepth: 1
changes
licencing
.. _Python: http://www.python.org/
.. _libudev: http://www.freedesktop.org/software/systemd/man/libudev.html
.. _librelist.com: http://librelist.com/
.. _list archives: http://librelist.com/browser/pyudev/
.. _issue tracker: https://github.com/lunaryorn/pyudev/issues
.. _GitHub: https://github.com/lunaryorn/pyudev
pyudev-0.21.0/doc/contribute.rst 0000664 0001750 0001750 00000002746 12654153421 017747 0 ustar mulhern mulhern 0000000 0000000 Contribute
==========
Please fork the repository, and send pull requests with new features or bug
fixes, but respect the following guidelines:
- Read `how to properly contribute to open source projects on GitHub
`_.
- Understand the `branching model `_.
- Use a topic branch based on the ``develop`` branch to easily amend a pull
request later, if necessary.
- Write `good commit messages `_.
- Squash commits on the topic branch before opening a pull request.
- Respect :pep:`8` (use pep8_ to check your coding style compliance).
- Add unit tests if possible (refer to the :doc:`testsuite documentation
`).
- Add API documentation in docstrings.
- Open a `pull request`_.
that relates to but one subject with a clear title and description in
grammatically correct, complete sentences.
Complying to these guidelines greatly increase the change of getting your pull
request merged. You will be asked to improve your changeset if your pull
request breaks any of the above guidelines.
If you intend to make larger changes, especially if these changes break the ABI,
please ask on the mailing list first.
.. _pep8: http://pypi.python.org/pypi/pep8/
.. _contribute: http://gun.io/blog/how-to-github-fork-branch-and-pull-request/
.. _branching: http://nvie.com/posts/a-successful-git-branching-model/
.. _commits: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
.. _pull request: https://help.github.com/articles/using-pull-requests
pyudev-0.21.0/doc/install.rst 0000664 0001750 0001750 00000002457 12660444030 017232 0 ustar mulhern mulhern 0000000 0000000 Installation
============
Python versions and implementations
-----------------------------------
pyudev supports CPython from 2.6 up to the latest Python 3 release, and PyPy
1.5. Jython may work, too, but is not tested. Generally any Python
implementation compatible with CPython 2.6 should work.
Dependencies
------------
pyudev needs libudev 151 and newer, earlier versions of libudev as found on
dated Linux systems may work, but are not tested and not officially supported.
It is written in pure Python based on :mod:`ctypes`, so no compilers or headers
are required for installation.
To use any of the toolkit integration modules. the corresponding toolkit must be
available, but no toolkit is required during installation.
Installation from Cheeseshop
----------------------------
Install pyudev from the Cheeseshop_ with pip_::
pip install pyudev
Installation from source code
-----------------------------
Close the public repository::
git clone https://github.com/lunaryorn/pyudev.git
Or download `tarball `_::
curl -OL https://github.com/lunaryorn/pyudev/tarball/master
Then install pyudev from the source code tree::
python setup.py install
.. _Cheeseshop: http://pypi.python.org/pypi/pyudev
.. _pip: http://www.pip-installer.org/
pyudev-0.21.0/doc/guide.rst 0000664 0001750 0001750 00000034151 12744230565 016666 0 ustar mulhern mulhern 0000000 0000000 User guide
==========
.. currentmodule:: pyudev
This guide gives an introduction in how to use pyudev for common operations
like device enumeration or monitoring:
.. contents::
A detailed reference is provided in the :doc:`API documentation `.
Getting started
---------------
Import pyudev and verify that you're using the latest version:
>>> import pyudev
>>> pyudev.__version__
u'0.16'
>>> pyudev.udev_version()
181
This prints the version of pyudev itself and of the underlying libudev_.
A note on versioning
--------------------
pyudev supports libudev_ 151 or newer, but still tries to cover the most recent
libudev_ API completely. If you are using older libudev_ releases, some
functionality of pyudev may be unavailable, simply because libudev_ is too old
to support a specific feature. Whenever this is the case, the minimum required
version of udev is noted in the documentation (see
:attr:`Device.is_initialized` for an example). If no version is specified for
an attribute or a method, it is available on all supported libudev_ versions.
You can check the version of the underlying libudev_ with
:func:`pyudev.udev_version()`.
Enumerating devices
-------------------
A common use case is to enumerate available devices, or a subset thereof. But
before you can do anything with pyudev, you need to establish a "connection" to
the udev device database first. This connection is represented by a library
:class:`Context`:
>>> context = pyudev.Context()
The :class:`Context` is the central object of pyudev and libudev_. You will
need a :class:`Context` object for almost anything in pyudev. With the
``context`` you can now enumerate the available devices:
>>> for device in context.list_devices(): # doctest: +ELLIPSIS
... device
...
Device(u'/sys/devices/LNXSYSTM:00')
Device(u'/sys/devices/LNXSYSTM:00/LNXCPU:00')
Device(u'/sys/devices/LNXSYSTM:00/LNXCPU:01')
...
By default, :meth:`list_devices()` yields all devices available on the system
as :class:`Device` objects, but you can filter the list of devices with keyword
arguments to enumerate all available partitions for example:
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print(device)
...
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda1')
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda2')
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda/sda3')
The choice of the right filters depends on the use case and generally requires
some knowledge about how udev classifies and categorizes devices. This is out
of the scope of this guide. Poke around in ``/sys/`` to get a feeling for the
udev-way of device handling, read the udev documentation or one of the
tutorials in the net.
The keyword arguments of :meth:`list_devices()` provide the most common filter
operations. You can apply other, less common filters by calling one of the
``match_*`` methods on the :class:`Enumerator` returned by of
:meth:`list_devices()`.
Accessing individual devices directly
-------------------------------------
If you just need a single specific :class:`Device`, you don't need to enumerate
all devices with a specific filter criterion. Instead, you can directly create
:class:`Device` objects from a device path (:meth:`Devices.from_path()`), by
from a subsystem and device name (:meth:`Devices.from_name()`) or from a device
file (:meth:`Devices.from_device_file()`). The following code gets the
:class:`Device` object for the first hard disc in three different ways:
>>> pyudev.Devices.from_path(context, '/sys/block/sda')
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
>>> pyudev.Devices.from_name(context, 'block', 'sda')
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
>>> pyudev.Devices.from_device_file(context, '/dev/sda')
Device(u'/sys/devices/pci0000:00/0000:00:0d.0/host2/target2:0:0/2:0:0:0/block/sda')
As you can see, you need to pass a :class:`Context` to both methods as
reference to the udev database from which to retrieve information about the
device.
.. note::
The :class:`Device` objects created in the above example refer to the same
device. Consequently, they are considered equal:
>>> pyudev.Devices.from_path(context, '/sys/block/sda') == pyudev.Devices.from_name(context, 'block', 'sda')
True
Whereas :class:`Device` objects referring to different devices are unequal:
>>> pyudev.Devices.from_name(context, 'block', 'sda') == pyudev.Devices.from_name(context, 'block', 'sda1')
False
Querying device information
---------------------------
As you've seen, :class:`Device` represents a device in the udev database. Each
such device has a set of "device properties" (not to be confused with Python
properties as created by :func:`property()`!) that describe the capabilities
and features of this device as well as its relationship to other devices.
Common device properties are also available as properties of a :class:`Device`
object. For instance, you can directly query the :attr:`device_node` and the
:attr:`device_type` of block devices:
>>> for device in context.list_devices(subsystem='block'):
... print('{0} ({1})'.format(device.device_node, device.device_type))
...
/dev/sr0 (disk)
/dev/sda (disk)
/dev/sda1 (partition)
/dev/sda2 (partition)
/dev/sda3 (partition)
For all other properties, :class:`Device` provides a dictionary-like interface
to directly access the device properties. You'll get the same information as
with the generic properties:
>>> for device in context.list_devices(subsystem='block'):
... print('{0} ({1})'.format(device['DEVNAME'], device['DEVTYPE']))
...
/dev/sr0 (disk)
/dev/sda (disk)
/dev/sda1 (partition)
/dev/sda2 (partition)
/dev/sda3 (partition)
.. warning::
When filtering devices, you have to use the device property names. The
names of corresponding properties of :class:`Device` will generally **not**
work. Compare the following two statements:
>>> [device.device_node for device in context.list_devices(subsystem='block', DEVTYPE='partition')]
[u'/dev/sda1', u'/dev/sda2', u'/dev/sda3']
>>> [device.device_node for device in context.list_devices(subsystem='block', device_type='partition')]
[]
But you can also query many device properties that are not available as Python
properties on the :class:`Device` object with a convenient mapping interface,
like the filesystem type. :class:`Device` provides a convenient mapping
interface for this purpose:
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print('{0} ({1})'.format(device.device_node, device.get('ID_FS_TYPE')))
...
/dev/sda1 (ext3)
/dev/sda2 (swap)
/dev/sda3 (ext4)
.. note::
Such device specific properties may not be available on devices. Either use
``get()`` to specify default values for missing properties, or be prepared
to catch :exc:`~exceptions.KeyError`.
Most device properties are computed by udev rules from the driver- and
device-specific "device attributes". The :attr:`Device.attributes` mapping
gives you access to these attributes, but generally you should not need these.
Use the device properties whenever possible.
Examing the device hierarchy
----------------------------
A :class:`Device` is part of a device hierarchy, and can have a
:attr:`~Device.parent` device that more or less resembles the physical
relationship between devices. For instance, the :attr:`~Device.parent` of
partition devices is a :class:`Device` object that represents the disc the
partition is located on:
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print('{0} is located on {1}'.format(device.device_node, device.parent.device_node))
...
/dev/sda1 is located on /dev/sda
/dev/sda2 is located on /dev/sda
/dev/sda3 is located on /dev/sda
Generally, you should not rely on the direct parent-child relationship between
two devices. Instead of accessing the parent directly, search for a parent
within a specific subsystem, e.g. for the parent ``block`` device, with
:meth:`~Device.find_parent()`:
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print('{0} is located on {1}'.format(device.device_node, device.find_parent('block').device_node))
...
/dev/sda1 is located on /dev/sda
/dev/sda2 is located on /dev/sda
/dev/sda3 is located on /dev/sda
This also save you the tedious work of traversing the device tree manually, if
you are interested in grand parents, like the name of the PCI slot of the SCSI
or IDE controller of the disc that contains a partition:
>>> for device in context.list_devices(subsystem='block', DEVTYPE='partition'):
... print('{0} attached to PCI slot {1}'.format(device.device_node, device.find_parent('pci')['PCI_SLOT_NAME']))
...
/dev/sda1 attached to PCI slot 0000:00:0d.0
/dev/sda2 attached to PCI slot 0000:00:0d.0
/dev/sda3 attached to PCI slot 0000:00:0d.0
Monitoring devices
------------------
Synchronous monitoring
~~~~~~~~~~~~~~~~~~~~~~
The Linux kernel emits events whenever devices are added, removed (e.g. a USB
stick was plugged or unplugged) or have their attributes changed (e.g. the
charge level of the battery changed). With :class:`pyudev.Monitor` you can
react on such events, for example to react on added or removed mountable
filesystems:
>>> monitor = pyudev.Monitor.from_netlink(context)
>>> monitor.filter_by('block')
>>> for device in iter(monitor.poll, None):
... if 'ID_FS_TYPE' in device:
... print('{0} partition {1}'.format(device.action, device.get('ID_FS_LABEL')))
...
add partition MULTIBOOT
remove partition MULTIBOOT
After construction of a monitor, you can install an event filter on the monitor
using :meth:`~Monitor.filter_by()`. In the above example only events from the
``block`` subsystem are handled.
.. note::
Always prefer :meth:`~Monitor.filter_by()` and
:meth:`~Monitor.filter_by_tag()` over manually filtering devices (e.g. by
``device.subsystem == 'block'`` or ``tag in device.tags``). These methods
install the filter on the *kernel side*. A process waiting for events is
thus only woken up for events that match these filters. This is much nicer
in terms of power consumption and system load than executing filters in the
process itself.
Eventually, you can receive events from the monitor. As you can see, a
:class:`Monitor` is iterable and synchronously yields occurred events. If you
iterate over a :class:`Monitor`, you will synchronously receive events in an
endless loop, until you raise an exception, or ``break`` the loop.
This is the quick and dirty way of monitoring, suitable for small scripts or
quick experiments. In most cases however, simply iterating over the monitor is
not sufficient, because it blocks the main thread, and can only be stopped if
an event occurs (otherwise the loop is not entered and you have no chance to
``break`` it).
Asynchronous monitoring
~~~~~~~~~~~~~~~~~~~~~~~
For such use cases, pyudev provides asynchronous monitoring with
:class:`MonitorObserver`. You can use it to log added and removed mountable
filesystems to a file, for example:
>>> monitor = pyudev.Monitor.from_netlink(context)
>>> monitor.filter_by('block')
>>> def log_event(action, device):
... if 'ID_FS_TYPE' in device:
... with open('filesystems.log', 'a+') as stream:
... print('{0} - {1}'.format(action, device.get('ID_FS_LABEL')), file=stream)
...
>>> observer = pyudev.MonitorObserver(monitor, log_event)
>>> observer.start()
The ``observer`` gets an event handler (``log_event()`` in this case) which is
asynchronously invoked on every event emitted by the underlying ``monitor``
after the observer has been started using :meth:`~threading.Thread.start()`.
.. warning::
The callback is invoked from a *different* thread than the one in which the
``observer`` was created. Be sure to protect access to shared resource
properly when you access them from the callback (e.g. by locking).
The ``observer`` can be stopped at any moment using :meth:`~MonitorObserver.stop()``:
>>> observer.stop()
.. warning::
Do *not* call :meth:`~MonitorObserver.stop()` from the event handler,
neither directly nor indirectly. Use :meth:`~MonitorObserver.send_stop()`
if you need to stop monitoring from inside the event handler.
GUI toolkit integration
~~~~~~~~~~~~~~~~~~~~~~~
If you're using a GUI toolkit, you already have the event system of the GUI
toolkit at hand. pyudev provides observer classes that seamlessly integration
in the event system of the GUI toolkit and relieve you from caring with
synchronisation issues that would occur with thread-based monitoring as
implemented by :class:`MonitorObserver`.
pyudev supports all major GUI toolkits available for Python:
- Qt_ 5 using :mod:`pyudev.pyqt5`
- Qt_ 4 using :mod:`pyudev.pyqt4` for the PyQt4_ binding or :mod:`pyudev.pyside`
for the PySide_ binding
- PyGtk_ 2 using :mod:`pyudev.glib`
- wxWidgets_ and wxPython_ using :mod:`pyudev.wx`
Each of these modules provides an observer class that observers the monitor
asynchronously and emits proper signals upon device events.
For instance, the above example would look like this in a PySide_ application:
>>> from pyudev.pyside import QUDevMonitorObserver
>>> monitor = pyudev.Monitor.from_netlink(context)
>>> observer = QUDevMonitorObserver(monitor)
>>> observer.deviceEvent.connect(log_event)
>>> monitor.start()
Device objects as booleans
~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of a Device object in a boolean context as a shorthand for a comparison
with None is an error.
The Device class inherits from the abstract Mapping class, as it maps udev
property names to their values. Consequently, if a Device object has no udev
properties, an unusual but not impossible occurance, the object is
interpreted as False in a boolean context.
.. _pypi: https://pypi.python.org/pypi/pyudev
.. _libudev: http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
.. _Qt: http://qt.io/developers/
.. _PyQt5: https://riverbankcomputing.co.uk/software/pyqt/intro
.. _PyQt4: https://riverbankcomputing.co.uk/software/pyqt/intro
.. _PySide: http://wiki.qt.io/PySide
.. _PyGtk: http://www.pygtk.org/
.. _wxWidgets: http://wxwidgets.org
.. _wxPython: http://www.wxpython.org
pyudev-0.21.0/doc/_templates/ 0000775 0001750 0001750 00000000000 12744231232 017160 5 ustar mulhern mulhern 0000000 0000000 pyudev-0.21.0/doc/_templates/info.html 0000664 0001750 0001750 00000001520 12654153421 021002 0 ustar mulhern mulhern 0000000 0000000 {% macro link(title, link, internal=false) -%}
{{ title }}
{%- endmacro %}
pyudev {{release}}
Install
Links
- pyudev on {{link('PyPI', 'https://pypi.python.org/pypi/pyudev')}} or
{{link('Crate.io', 'https://crate.io/packages/pyudev')}}
- pyudev on {{link('Read the Docs', 'http://readthedocs.org/projects/pyudev/?fromdocs=pyudev')}}
- pyudev on {{link('GitHub', 'https://github.com/lunaryorn/pyudev')}}
- pyudev on {{link('Travis CI', 'http://travis-ci.org/lunaryorn/pyudev')}}
Support
- {{link('issue tracker', 'https://github.com/lunaryorn/pyudev/issues')}}
pyudev-0.21.0/requirements.txt 0000664 0001750 0001750 00000000166 12744224632 017553 0 ustar mulhern mulhern 0000000 0000000 # unit test requirements
docutils>=0.9
pytest>=2.8
mock>=1.0b1
hypothesis
# documentation requirements
sphinx>=1.0.7