pax_global_header00006660000000000000000000000064146174621450014524gustar00rootroot0000000000000052 comment=5df7045aaa95e4b923c9a2f64fd5115b3642a69f pyudev-0.24.3/000077500000000000000000000000001461746214500131265ustar00rootroot00000000000000pyudev-0.24.3/.gitattributes000066400000000000000000000000521461746214500160160ustar00rootroot00000000000000* text *.py diff=python *.html diff=html pyudev-0.24.3/.github/000077500000000000000000000000001461746214500144665ustar00rootroot00000000000000pyudev-0.24.3/.github/workflows/000077500000000000000000000000001461746214500165235ustar00rootroot00000000000000pyudev-0.24.3/.github/workflows/main.yml000066400000000000000000000040041461746214500201700ustar00rootroot00000000000000--- name: pyudev CI # yamllint disable-line rule:truthy on: push: branches: [master] pull_request: branches: [master] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: python-checks: strategy: matrix: include: # MANDATORY CHECKS USING CURRENT DEVELOPMENT INTERPRETER - python-version: "3.12" dependencies: pytest=="7" hypothesis task: PYTHONPATH=./src make -f Makefile test-travis # MANDATORY CHECKS USING LOWEST SUPPORTED INTERPRETER - python-version: "3.10" dependencies: pytest=="7" hypothesis task: PYTHONPATH=./src make -f Makefile test-travis # MANDATORY CHECKS USING PYPY INTERPRETER - python-version: pypy-3.9 dependencies: pytest=="7" hypothesis task: PYTHONPATH=./src make -f Makefile test-travis runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt-get -q update sudo apt-get -y install libudev-dev - name: Install dependencies run: pip3 install ${{ matrix.dependencies }} - name: Run test run: ${{ matrix.task }} development_environment_checks: strategy: matrix: include: - dependencies: black python3-isort task: fmt-travis - dependencies: yamllint task: yamllint - dependencies: python python3-build twine task: package - dependencies: pylint task: lint runs-on: ubuntu-latest container: fedora:40 # CURRENT DEVELOPMENT ENVIRONMENT steps: - uses: actions/checkout@v4 - name: Install dependencies run: > dnf install -y make ${{ matrix.dependencies }} - name: Run test run: make -f Makefile ${{ matrix.task }} pyudev-0.24.3/.gitignore000066400000000000000000000006101461746214500151130ustar00rootroot00000000000000# Python bytecode *.pyc # distutils/distribute metadata and outputs src/pyudev.egg-info/* build/* dist/* # Sphinx outputs doc/_build/* # Test environments and results .tox/* *-tests.xml # Rope configuration .ropeproject/* # Vagrant files .vagrant # coverage files htmlcov .coverage # pyreverse files _pyreverse # hypothesis files .hypothesis # vim *.swp # zip *.gz # pytest .cache pyudev-0.24.3/.isort.cfg000066400000000000000000000010211461746214500150170ustar00rootroot00000000000000[settings] multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True line_length=88 sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCAL,LOCALFOLDER default_section=THIRDPARTY import_heading_future=isort: FUTURE import_heading_stdlib=isort: STDLIB import_heading_thirdparty=isort: THIRDPARTY import_heading_firstparty=isort: FIRSTPARTY import_heading_local=isort: LOCAL # All items above should be the same for every # Stratis project. The items below vary with # each project. known_local=pyudev pyudev-0.24.3/CHANGES.rst000066400000000000000000000343471461746214500147430ustar00rootroot000000000000000.24.3 ====== Recommended development release: Fedora 40 0.24.2 ====== Recommended development release: Fedora 40 - Update release infrastructure: https://github.com/pyudev/pyudev/pull/508 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/507 https://github.com/pyudev/pyudev/pull/506 https://github.com/pyudev/pyudev/pull/505 https://github.com/pyudev/pyudev/pull/503 https://github.com/pyudev/pyudev/pull/498 https://github.com/pyudev/pyudev/pull/497 https://github.com/pyudev/pyudev/pull/492 https://github.com/pyudev/pyudev/pull/491 https://github.com/pyudev/pyudev/pull/489 0.24.1 ====== Recommended development release: Fedora 37 - Add support for PySide6: https://github.com/pyudev/pyudev/pull/487 - Add missing 'priority' argument for GLib.to_add_watch() https://github.com/pyudev/pyudev/pull/479 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/486 https://github.com/pyudev/pyudev/pull/485 https://github.com/pyudev/pyudev/pull/484 https://github.com/pyudev/pyudev/pull/481 https://github.com/pyudev/pyudev/pull/480 https://github.com/pyudev/pyudev/pull/477 https://github.com/pyudev/pyudev/pull/474 0.24.0 ====== Recommended development release: Fedora 36 - Remove a bunch of Python 2 code: https://github.com/pyudev/pyudev/pull/468 https://github.com/pyudev/pyudev/pull/460 https://github.com/pyudev/pyudev/pull/455 https://github.com/pyudev/pyudev/pull/358 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/471 https://github.com/pyudev/pyudev/pull/466 https://github.com/pyudev/pyudev/pull/465 https://github.com/pyudev/pyudev/pull/464 https://github.com/pyudev/pyudev/pull/462 https://github.com/pyudev/pyudev/pull/461 https://github.com/pyudev/pyudev/pull/459 https://github.com/pyudev/pyudev/pull/458 https://github.com/pyudev/pyudev/pull/456 https://github.com/pyudev/pyudev/pull/454 https://github.com/pyudev/pyudev/pull/447 https://github.com/pyudev/pyudev/pull/445 0.23.2 ====== Recommended development release: Fedora 35 - New release: 0.23.2: https://github.com/pyudev/pyudev/pull/444 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/445 https://github.com/pyudev/pyudev/pull/443 https://github.com/pyudev/pyudev/pull/442 0.23.1 ====== Recommended development release: Fedora 34 - Update to version 0.23.1: https://github.com/pyudev/pyudev/pull/440 - Check for existence of _hasattr attribute in Context.__del__ method: https://github.com/pyudev/pyudev/issues/421 https://github.com/pyudev/pyudev/pull/439 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/438 0.23.0 ====== Recommended development release: Fedora 34 - Update to version 0.23.0: https://github.com/pyudev/pyudev/pull/427 - Officially drop support for Python 2: https://github.com/pyudev/pyudev/pull/414 - Remove external mock dependency: https://github.com/pyudev/pyudev/pull/409 - Enable GLib unit tests: https://github.com/pyudev/pyudev/pull/400 - Switch to new style gi.repository.GLib imports: https://github.com/pyudev/pyudev/pull/399 - Tidies and Maintenance: https://github.com/pyudev/pyudev/pull/432 https://github.com/pyudev/pyudev/pull/430 https://github.com/pyudev/pyudev/pull/429 https://github.com/pyudev/pyudev/pull/428 https://github.com/pyudev/pyudev/pull/424 https://github.com/pyudev/pyudev/pull/423 https://github.com/pyudev/pyudev/pull/418 https://github.com/pyudev/pyudev/pull/415 https://github.com/pyudev/pyudev/pull/413 0.22.0 (Jan, 2020) ================== - Add a six-enabled move for collections move imports: https://github.com/pyudev/pyudev/pull/386 - Fix any newly introduced pylint errors - Numerous improvements or updates to the test infrastructure - A number of test updates - Require yapf 0.21.0 for Python formatting - Various documentation fixes and updates 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.24.3/COPYING000066400000000000000000000635001461746214500141650ustar00rootroot00000000000000 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.24.3/Makefile000066400000000000000000000027111461746214500145670ustar00rootroot00000000000000# Copyright (C) 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 package: (umask 0022; python -m build; python -m twine check --strict ./dist/*) lint: pylint setup.py pylint src/pyudev/_os PYREVERSE_OPTS = --output=pdf view: -rm -Rf _pyreverse mkdir _pyreverse PYTHONPATH=src pyreverse ${PYREVERSE_OPTS} --project="pyudev" src/pyudev mv classes_pyudev.pdf _pyreverse mv packages_pyudev.pdf _pyreverse archive: git archive --output=./archive.tar.gz HEAD .PHONY: test-travis test-travis: py.test --junitxml=tests.xml -rfEsxX .PHONY: fmt fmt: isort setup.py src tests black . .PHONY: fmt-travis fmt-travis: isort --diff --check-only setup.py src tests black . --check .PHONY: yamllint yamllint: yamllint --strict .github/workflows/*.yml pyudev-0.24.3/README.rst000066400000000000000000000071741461746214500146260ustar00rootroot00000000000000###### 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_ 3 and compatible versions of PyPy_. 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 `_. - Write `good commit messages `_. - 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: https://www.freedesktop.org/software/systemd/man/libudev.html .. _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/pyudev/pyudev/issues .. _GitHub: http://github.com/pyudev/pyudev .. _git: http://www.git-scm.com/ Request a New Release --------------------- I will be doing regular releases of this project every August and October, shortly after Fedora releases are branched from rawhide. If you believe an extra release would help you in some way, please file an issue, explaining why you need the new release, and I expect I'll put one up. Why should you explain why you need the new release? Well, it is helpful to me, because pyudev is not at all part of my regular work, and I tend not to know very much about how it is used these days. pyudev-0.24.3/doc/000077500000000000000000000000001461746214500136735ustar00rootroot00000000000000pyudev-0.24.3/doc/_templates/000077500000000000000000000000001461746214500160305ustar00rootroot00000000000000pyudev-0.24.3/doc/_templates/info.html000066400000000000000000000015201461746214500176470ustar00rootroot00000000000000{% macro link(title, link, internal=false) -%} {{ title }} {%- endmacro %}

pyudev {{release}}

Install

  • pip install pyudev

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.24.3/doc/api/000077500000000000000000000000001461746214500144445ustar00rootroot00000000000000pyudev-0.24.3/doc/api/index.rst000066400000000000000000000004341461746214500163060ustar00rootroot00000000000000API 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.24.3/doc/api/pyudev.glib.rst000066400000000000000000000045561461746214500174400ustar00rootroot00000000000000: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.24.3/doc/api/pyudev.pyqt4.rst000066400000000000000000000037721461746214500176030ustar00rootroot00000000000000: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.24.3/doc/api/pyudev.pyqt5.rst000066400000000000000000000014711461746214500175760ustar00rootroot00000000000000: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.24.3/doc/api/pyudev.pyside.rst000066400000000000000000000040001461746214500200000ustar00rootroot00000000000000: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.24.3/doc/api/pyudev.rst000066400000000000000000000126171461746214500165210ustar00rootroot00000000000000: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.24.3/doc/api/pyudev.wx.rst000066400000000000000000000050241461746214500171500ustar00rootroot00000000000000: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.24.3/doc/changes.rst000066400000000000000000000000611461746214500160320ustar00rootroot00000000000000Changelog ********* .. include:: ../CHANGES.rst pyudev-0.24.3/doc/conf.py000066400000000000000000000101041461746214500151660ustar00rootroot00000000000000# -*- 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 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: """ 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 = "pyudev" copyright = "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("autodoc-process-docstring", cut_lines(2, what=["module"])) app.add_directive("udevversion", UDevVersion) pyudev-0.24.3/doc/contribute.rst000066400000000000000000000027461461746214500166140ustar00rootroot00000000000000Contribute ========== 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.24.3/doc/endorsements.rst000066400000000000000000000007071461746214500171370ustar00rootroot00000000000000pyudev 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.24.3/doc/guide.rst000066400000000000000000000341531461746214500155300ustar00rootroot00000000000000User 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 other udev properties, :class:`Device` provides a mapping interface to access the device properties by means of its properties attribute. >>> for device in context.list_devices(subsystem='block'): ... print('{0} ({1})'.format(device.properties['DEVNAME'], device.properties['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.properties.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(device): ... if 'ID_FS_TYPE' in device.properties: ... with open('filesystems.log', 'a+') as stream: ... print('{0} - {1}'.format(device.action, device.get('ID_FS_LABEL')), file=stream) ... >>> observer = pyudev.MonitorObserver(monitor, callback=log_event) >>> observer.start() The ``observer`` gets a callback (``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.24.3/doc/index.rst000066400000000000000000000051641461746214500155420ustar00rootroot00000000000000pyudev -- pure Python libudev_ binding ====================================== pyudev |release| (:doc:`changes`, :doc:`installation `) pyudev is a :doc:`LGPL licenced `, pure Python_ 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.pyqt5`, :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.24.3/doc/install.rst000066400000000000000000000022421461746214500160730ustar00rootroot00000000000000Installation ============ Python versions and implementations ----------------------------------- pyudev supports CPython 3 and compatible versions of PyPy. 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.24.3/doc/licencing.rst000066400000000000000000000000641461746214500163600ustar00rootroot00000000000000Licencing ========= .. literalinclude:: ../COPYING pyudev-0.24.3/doc/tests/000077500000000000000000000000001461746214500150355ustar00rootroot00000000000000pyudev-0.24.3/doc/tests/index.rst000066400000000000000000000007051461746214500167000ustar00rootroot00000000000000Testsuite 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.24.3/doc/tests/plugins.rst000066400000000000000000000026701461746214500172550ustar00rootroot00000000000000:mod:`plugins` – Testsuite plugins ================================== .. automodule:: plugins The following plugins are provided and enabled: .. autosummary:: .fake_monitor .mock_libudev .travis :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.24.3/doc/tests/running.rst000066400000000000000000000010531461746214500172460ustar00rootroot00000000000000Test running ============ See the current CI configuration file to determine what tests are currently being run and how to run them. 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. pyudev-0.24.3/pyproject.toml000066400000000000000000000001211461746214500160340ustar00rootroot00000000000000[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" pyudev-0.24.3/pytest.ini000066400000000000000000000001271461746214500151570ustar00rootroot00000000000000[tool:pytest] # do not search for tests in build directory norecursedirs = .* _* build pyudev-0.24.3/setup.cfg000066400000000000000000000017671461746214500147620ustar00rootroot00000000000000[metadata] name = pyudev author = Sebastian Wiesner author_email = lunaryorn@gmail.com url = http://github.com/pyudev/pyudev description = A libudev binding license = LGPL 2.1+ long_description = file: README.rst long_description_content_type = text/x-rst version = attr: pyudev.version.__version__ platforms = Linux 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 :: 3 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries Topic :: System :: Hardware Topic :: System :: Operating System Kernels :: Linux [options] python_requires = >=3.7 package_dir = =src packages = pyudev pyudev._ctypeslib pyudev.device pyudev._os pyudev-0.24.3/setup.py000066400000000000000000000016211461746214500146400ustar00rootroot00000000000000#!/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 """ setup.py """ # isort: THIRDPARTY import setuptools setuptools.setup() pyudev-0.24.3/src/000077500000000000000000000000001461746214500137155ustar00rootroot00000000000000pyudev-0.24.3/src/pyudev/000077500000000000000000000000001461746214500152315ustar00rootroot00000000000000pyudev-0.24.3/src/pyudev/__init__.py000066400000000000000000000037471461746214500173550ustar00rootroot00000000000000# -*- 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 """ # isort: LOCAL from pyudev._errors import ( DeviceNotFoundAtPathError, DeviceNotFoundByFileError, DeviceNotFoundByNameError, DeviceNotFoundByNumberError, DeviceNotFoundError, DeviceNotFoundInEnvironmentError, ) from pyudev._util import udev_version from pyudev.core import Context, Enumerator from pyudev.device import Attributes, Device, Devices, Tags from pyudev.discover import ( DeviceFileHypothesis, DeviceNameHypothesis, DeviceNumberHypothesis, DevicePathHypothesis, Discovery, ) from pyudev.monitor import Monitor, MonitorObserver from pyudev.version import __version__, __version_info__ pyudev-0.24.3/src/pyudev/_ctypeslib/000077500000000000000000000000001461746214500173665ustar00rootroot00000000000000pyudev-0.24.3/src/pyudev/_ctypeslib/__init__.py000066400000000000000000000017011461746214500214760ustar00rootroot00000000000000# -*- 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, libudev pyudev-0.24.3/src/pyudev/_ctypeslib/_errorcheckers.py000066400000000000000000000060151461746214500227420ustar00rootroot00000000000000# -*- 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. """ # isort: STDLIB import errno import os 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 None: return EnvironmentError(errnum, errorstr) return exception(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.24.3/src/pyudev/_ctypeslib/libc.py000066400000000000000000000022651461746214500206560ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB 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.24.3/src/pyudev/_ctypeslib/libudev.py000066400000000000000000000245521461746214500214020ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB from ctypes import POINTER, Structure, c_char, c_char_p, c_int, c_uint, c_ulonglong from ._errorcheckers import ( check_errno_on_nonzero_return, check_errno_on_null_pointer_return, 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.24.3/src/pyudev/_ctypeslib/utils.py000066400000000000000000000044421461746214500211040ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB 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.24.3/src/pyudev/_errors.py000066400000000000000000000116521461746214500172630ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import abc class DeviceError(Exception): """ Any error raised when messing around w/ or trying to discover devices. """ __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. """ __metaclass__ = abc.ABCMeta 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 pages. """ 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) return self._FMT_STR % (self._value, self._param) pyudev-0.24.3/src/pyudev/_os/000077500000000000000000000000001461746214500160115ustar00rootroot00000000000000pyudev-0.24.3/src/pyudev/_os/__init__.py000066400000000000000000000017231461746214500201250ustar00rootroot00000000000000# -*- 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, poll pyudev-0.24.3/src/pyudev/_os/pipe.py000066400000000000000000000105141461746214500173210ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import fcntl import os from functools import partial # isort: LOCAL from pyudev._ctypeslib.libc import ERROR_CHECKERS, FD_PAIR, 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 file_descriptor in fds: set_fd_status_flag(file_descriptor, os.O_NONBLOCK) if flags & O_CLOEXEC != 0: for file_descriptor in fds: set_fd_flag(file_descriptor, 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 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): # pylint: disable=invalid-name """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): # pylint: disable=invalid-name """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: """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.24.3/src/pyudev/_os/poll.py000066400000000000000000000076631461746214500173450ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import select # isort: LOCAL from pyudev._util import eintr_retry_call class Poll: """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: # pylint: disable=invalid-name mask = cls._EVENT_TO_MASK.get(event) if not mask: raise ValueError(f"Unknown event type: {repr(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: # pylint: disable=invalid-name if self._has_event(event_mask, select.POLLNVAL): raise IOError(f"File descriptor not open: {repr(fd)}") if self._has_event(event_mask, select.POLLERR): raise IOError(f"Error while polling fd: {repr(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.24.3/src/pyudev/_qt_base.py000066400000000000000000000153621461746214500173670ustar00rootroot00000000000000# -*- 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 """ # isort: LOCAL from pyudev.device import Device class MonitorObserverMixin: """ 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, } # isort: STDLIB 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: """ 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: """ 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(str, 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.24.3/src/pyudev/_util.py000066400000000000000000000151441461746214500167240ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import errno import os import stat import sys from subprocess import check_output 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, str): 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 return ensure_byte_string(str(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 # isort: STDLIB 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.24.3/src/pyudev/core.py000066400000000000000000000316621461746214500165430ustar00rootroot00000000000000# -*- 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 """ # isort: LOCAL from pyudev._ctypeslib.libudev import ERROR_CHECKERS, SIGNATURES from pyudev._ctypeslib.utils import load_ctypes_library from pyudev._errors import DeviceNotFoundAtPathError from pyudev._util import ( ensure_byte_string, ensure_unicode_string, property_value_to_bytes, udev_list_iterate, ) from pyudev.device import Devices class Context: """ 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): if hasattr(self, "_libudev"): 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)) return "/sys" # Fixed path since udev 183 @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)) return "/dev" # Fixed path since udev 183 @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)) 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: """ 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.24.3/src/pyudev/device/000077500000000000000000000000001461746214500164705ustar00rootroot00000000000000pyudev-0.24.3/src/pyudev/device/__init__.py000066400000000000000000000017641461746214500206110ustar00rootroot00000000000000# -*- 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, Device, Devices, Tags pyudev-0.24.3/src/pyudev/device/_device.py000066400000000000000000001267751461746214500204620ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import collections import os import re import sys from datetime import timedelta # isort: LOCAL from pyudev._errors import ( DeviceNotFoundAtPathError, DeviceNotFoundByFileError, DeviceNotFoundByInterfaceIndexError, DeviceNotFoundByKernelDeviceError, DeviceNotFoundByNameError, DeviceNotFoundByNumberError, DeviceNotFoundInEnvironmentError, ) from pyudev._util import ( ensure_byte_string, ensure_unicode_string, get_device_type, string_to_bool, udev_list_iterate, ) # pylint: disable=too-many-lines class Devices: """ 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(collections.abc.Mapping): # pylint: disable=too-many-public-methods """ A single device with attached attributes and 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:: Currently, Device extends Mapping. The mapping that it stores is that of udev property names to udev property values. This use is deprecated and Device will no longer extend Mapping in 1.0. To look up udev properties, use the Device.properties property. .. 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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): collections.abc.Mapping.__init__(self) 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. """ # isort: STDLIB 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 None: return None return ensure_unicode_string(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 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. :returns: an Attributes object, useful for reading attributes :rtype: Attributes .. 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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. """ # isort: STDLIB 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 return self.device_path == other def __ne__(self, other): if isinstance(other, Device): return self.device_path != other.device_path 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(collections.abc.Mapping): """ udev properties :class:`Device` objects. .. versionadded:: 0.21 """ def __init__(self, device): collections.abc.Mapping.__init__(self) 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: """ 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(collections.abc.Iterable, collections.abc.Container): """ A iterable over :class:`Device` tags. Subclasses the ``Container`` and the ``Iterable`` ABC. """ # pylint: disable=too-few-public-methods def __init__(self, device): # pylint: disable=super-init-not-called collections.abc.Iterable.__init__(self) 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)) ) return any(t == tag for t in self) # pragma: no cover 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.24.3/src/pyudev/discover.py000066400000000000000000000260771461746214500174350ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import abc import functools import os import re # isort: LOCAL from pyudev._errors import DeviceNotFoundError from pyudev.device import Devices 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 class Hypothesis: """ Represents a hypothesis about the meaning of the device identifier. """ __metaclass__ = abc.ABCMeta @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() 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: # 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.24.3/src/pyudev/glib.py000066400000000000000000000145201461746214500165220ustar00rootroot00000000000000# -*- 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:`gi.repository.GLib` and :mod:`gi.repository.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 """ # isort: THIRDPARTY from gi.repository import GLib, GObject # pylint: disable=import-error class _ObserverMixin: """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 # pylint: disable=no-member self.event_source = GLib.io_add_watch( self.monitor, GLib.PRIORITY_DEFAULT, GLib.IO_IN, self._process_udev_event, ) elif not value and self.event_source is not None: # pylint: disable=no-member GLib.source_remove(self.event_source) def _process_udev_event(self, source, condition): # pylint: disable=unused-argument # pylint: disable=no-member 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.Object, _ObserverMixin): # pylint: disable=too-few-public-methods """ An observer for device events integrating into the :mod:`gi.repository.GLib` mainloop. This class inherits :class:`~gi.repository.GObject.Object` 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:`gi.repository.GObject.Object`. """ __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.Object.__init__(self) self._setup_observer(monitor) GObject.type_register(MonitorObserver) class GUDevMonitorObserver(GObject.Object, _ObserverMixin): # pylint: disable=too-few-public-methods """ An observer for device events integrating into the :mod:`gi.repository.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.Object.__init__(self) self._setup_observer(monitor) # isort: STDLIB 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.24.3/src/pyudev/monitor.py000066400000000000000000000510301461746214500172710ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import errno import os from functools import partial from threading import Thread # isort: LOCAL from pyudev._os import pipe, poll from pyudev._util import eintr_retry_call, ensure_byte_string from pyudev.device import Device class Monitor: """ 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): """ 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. """ # isort: STDLIB 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() 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. """ # isort: STDLIB 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`. """ # isort: STDLIB 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 ): # pylint: disable=keyword-arg-before-vararg """ 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: # isort: STDLIB 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.24.3/src/pyudev/pyqt4.py000066400000000000000000000034071461746214500166700ustar00rootroot00000000000000# -*- 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.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 """ # isort: THIRDPARTY from PyQt4 import QtCore # pylint: disable=import-error from ._qt_base import MonitorObserverGenerator, 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.24.3/src/pyudev/pyqt5.py000066400000000000000000000026501461746214500166700ustar00rootroot00000000000000# -*- 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 """ # isort: THIRDPARTY from PyQt5 import QtCore # pylint: disable=import-error from ._qt_base import MonitorObserverGenerator # pylint: disable=invalid-name MonitorObserver = MonitorObserverGenerator.make_monitor_observer( QtCore.QObject, QtCore.pyqtSignal, QtCore.QSocketNotifier ) pyudev-0.24.3/src/pyudev/pyside.py000066400000000000000000000034071461746214500171040ustar00rootroot00000000000000# -*- 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.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 """ # isort: THIRDPARTY from PySide import QtCore # pylint: disable=import-error from ._qt_base import MonitorObserverGenerator, 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.24.3/src/pyudev/pyside6.py000066400000000000000000000030021461746214500171610ustar00rootroot00000000000000# -*- 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.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 """ # isort: THIRDPARTY from PySide6 import QtCore # pylint: disable=import-error from ._qt_base import MonitorObserverGenerator # pylint: disable=invalid-name MonitorObserver = MonitorObserverGenerator.make_monitor_observer( QtCore.QObject, QtCore.Signal, QtCore.QSocketNotifier ) pyudev-0.24.3/src/pyudev/version.py000066400000000000000000000021001461746214500172610ustar00rootroot00000000000000# -*- 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, 24, 3, "") __version__ = "%s%s" % ( ".".join(str(x) for x in __version_info__[:3]), "".join(str(x) for x in __version_info__[3:]), ) pyudev-0.24.3/src/pyudev/wx.py000066400000000000000000000111331461746214500162400ustar00rootroot00000000000000# -*- 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 """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 """ # isort: THIRDPARTY from wx import EvtHandler, PostEvent # pylint: disable=import-error from wx.lib.newevent import NewEvent # pylint: disable=import-error, no-name-in-module # isort: LOCAL # for some reason, pylint thinks pyudev is a third party import import pyudev # pylint: disable=wrong-import-order DeviceEvent, EVT_DEVICE_EVENT = NewEvent() # pylint: disable=invalid-name 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() # pylint: disable=invalid-name DeviceRemovedEvent, EVT_DEVICE_REMOVED = NewEvent() # pylint: disable=invalid-name DeviceChangedEvent, EVT_DEVICE_CHANGED = NewEvent() # pylint: disable=invalid-name DeviceMovedEvent, EVT_DEVICE_MOVED = NewEvent() # pylint: disable=invalid-name 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) # isort: STDLIB 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.24.3/tests/000077500000000000000000000000001461746214500142705ustar00rootroot00000000000000pyudev-0.24.3/tests/__init__.py000066400000000000000000000000001461746214500163670ustar00rootroot00000000000000pyudev-0.24.3/tests/_constants.py000066400000000000000000000074641461746214500170300ustar00rootroot00000000000000# -*- 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 """ # isort: THIRDPARTY import pytest from hypothesis import strategies # isort: LOCAL from pyudev import Context, DeviceNotFoundError, Devices 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 = [d for d in udev.DeviceDatabase.db()] _DEVICES = [Devices.from_path(_CONTEXT, d.device_path) for d in _DEVICE_DATA] def device_strategy(require_existing=True, filter_func=lambda x: True): """ Strategy that yields filtered devices. The devices are filtered before being sampled to reduce the number of health failures. :param bool require_existing: at the very last, verify existance :param filter_func: a function to be used as a filter :type filter_func: Device -> bool """ strategy = strategies.sampled_from( [x for x in _CONTEXT.list_devices() if filter_func(x)] ) if require_existing: strategy = strategy.filter(_check_device) return strategy _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([p for p in 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([a for a in 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: [t for t in d.tags]) # an arbitrary tag belonging to a given device _TAG_STRATEGY = _TAGS_STRATEGY.filter(lambda t: t != []).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.24.3/tests/_device_tests/000077500000000000000000000000001461746214500171105ustar00rootroot00000000000000pyudev-0.24.3/tests/_device_tests/__init__.py000066400000000000000000000016601461746214500212240ustar00rootroot00000000000000# -*- 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.24.3/tests/_device_tests/_attributes_tests.py000066400000000000000000000106521461746214500232350ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import os import stat # isort: THIRDPARTY import pytest from hypothesis import given, settings, strategies # isort: LOCAL from pyudev import Devices from ..utils import is_unicode_string from ._device_tests import _CONTEXT_STRATEGY, _DEVICE_DATA, _DEVICES, _UDEV_TEST class TestAttributes: """ 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"): try: assert device.attributes.asbool(key) in (False, True) except KeyError: pass else: with pytest.raises(ValueError): try: device.attributes.asbool(key) except KeyError: pass pyudev-0.24.3/tests/_device_tests/_device_tests.py000066400000000000000000000471501461746214500223110ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import gc import operator import os import re from datetime import timedelta # isort: THIRDPARTY import pytest from hypothesis import given, settings, strategies # isort: LOCAL from pyudev import Device, Devices from pyudev.device import Attributes, Tags from .._constants import _CONTEXT, _CONTEXT_STRATEGY, _DEVICE_DATA, _DEVICES, _UDEV_TEST from ..utils import is_unicode_string try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock class TestDevice: """ 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) 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) 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) 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) def test_no_leak(self): """ 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 _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=1) 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: # Do not test equality of device properties with udevadm oracle. # https://bugzilla.redhat.com/show_bug.cgi?id=1787089 pass _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) 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) 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 == "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.24.3/tests/_device_tests/_devices_tests.py000066400000000000000000000172401461746214500224710ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import os import stat # isort: THIRDPARTY import pytest from hypothesis import assume, given, settings # isort: LOCAL from pyudev import ( DeviceNotFoundAtPathError, DeviceNotFoundByFileError, DeviceNotFoundByNameError, DeviceNotFoundByNumberError, DeviceNotFoundInEnvironmentError, Devices, ) from .._constants import ( _CONTEXT, _CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY, _UDEV_TEST, device_strategy, ) from ..utils import failed_health_check_wrapper class TestDevices: """ Test ``Devices`` methods. """ # pylint: disable=invalid-name @given(_CONTEXT_STRATEGY, device_strategy()) @settings(max_examples=5) def test_from_path(self, a_context, a_device): """ from_path() method yields correct device. """ assert a_device == Devices.from_path(a_context, a_device.device_path) @given(_CONTEXT_STRATEGY, device_strategy()) @settings(max_examples=5) def test_from_path_strips_leading_slash(self, a_context, a_device): """ from_path() yields the same value, even if initial '/' is missing. """ path = a_device.device_path assert Devices.from_path(a_context, path[1:]) == Devices.from_path( a_context, path ) @given(_CONTEXT_STRATEGY, device_strategy()) @settings(max_examples=5) def test_from_sys_path(self, a_context, a_device): """ from_sys_path() yields the correct device. """ assert a_device == Devices.from_sys_path(a_context, a_device.sys_path) def test_from_sys_path_device_not_found(self): """ Verify that a non-existant sys_path causes an exception. """ sys_path = "there_will_not_be_such_a_device" with pytest.raises(DeviceNotFoundAtPathError) as exc_info: Devices.from_sys_path(_CONTEXT, sys_path) error = exc_info.value assert error.sys_path == sys_path @given(_CONTEXT_STRATEGY, device_strategy()) @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, _SUBSYSTEM_STRATEGY) @settings(max_examples=5) def test_from_name_no_device_in_existing_subsystem(self, a_context, subsys): """ Verify that a real subsystem and non-existant name causes an exception to be raised. """ with pytest.raises(DeviceNotFoundByNameError) as exc_info: Devices.from_name(a_context, subsys, "foobar") error = exc_info.value assert error.subsystem == subsys assert error.sys_name == "foobar" def test_from_name_nonexisting_subsystem(self): """ Verify that a non-existant subsystem causes an exception. """ with pytest.raises(DeviceNotFoundByNameError) as exc_info: Devices.from_name(_CONTEXT, "no_such_subsystem", "foobar") error = exc_info.value assert error.subsystem == "no_such_subsystem" assert error.sys_name == "foobar" @failed_health_check_wrapper @given( _CONTEXT_STRATEGY, device_strategy(filter_func=lambda x: x.device_node is not None), ) @settings(max_examples=5) def test_from_device_number(self, a_context, a_device): """ Verify that from_device_number() yields the correct device. """ mode = os.stat(a_device.device_node).st_mode typ = "block" if stat.S_ISBLK(mode) else "char" device = Devices.from_device_number(a_context, typ, a_device.device_number) assert a_device == device @failed_health_check_wrapper @given( _CONTEXT_STRATEGY, device_strategy(filter_func=lambda x: x.device_node is not None), ) @settings(max_examples=5) def test_from_device_number_wrong_type(self, a_context, a_device): """ Verify appropriate behavior on real device number but swapped subsystems. """ mode = os.stat(a_device.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, a_device.device_number) # if it succeeds, the resulting device must not match the # one, we are actually looking for! assert device != a_device except DeviceNotFoundByNumberError as error: # check the correctness of the exception attributes assert error.device_type == typ assert error.device_number == a_device.device_number def test_from_device_number_invalid_type(self): """ Verify that a non-existant subsystem always results in an exception. """ with pytest.raises(DeviceNotFoundByNumberError): Devices.from_device_number(_CONTEXT, "foobar", 100) @failed_health_check_wrapper @given( _CONTEXT_STRATEGY, device_strategy(filter_func=lambda x: x.device_node is not None), ) @settings(max_examples=5) def test_from_device_file(self, a_context, a_device): """ Verify that from_device_file() yields correct device. """ device = Devices.from_device_file(a_context, a_device.device_node) assert a_device == device def test_from_device_file_no_device_file(self, tmpdir): """ Verify that a file that is not a device file will cause an exception to be raised. """ filename = tmpdir.join("test") filename.ensure(file=True) with pytest.raises(DeviceNotFoundByFileError): Devices.from_device_file(_CONTEXT, str(filename)) def test_from_device_file_non_existing(self, tmpdir): """ 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(_CONTEXT, str(filename)) @_UDEV_TEST(152, "test_from_environment") def test_from_environment(self): """ there is no device in a standard environment """ with pytest.raises(DeviceNotFoundInEnvironmentError): Devices.from_environment(_CONTEXT) pyudev-0.24.3/tests/_device_tests/_tags_tests.py000066400000000000000000000057061461746214500220110ustar00rootroot00000000000000# -*- 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 """ # isort: THIRDPARTY import pytest from hypothesis import given, settings, strategies # isort: LOCAL from pyudev import Devices from ..utils import is_unicode_string from ._device_tests import _CONTEXT_STRATEGY, _DEVICE_DATA, _DEVICES, _UDEV_TEST try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock class TestTags: """ 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) 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.24.3/tests/conftest.py000066400000000000000000000023261461746214500164720ustar00rootroot00000000000000# -*- 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 # isort: THIRDPARTY import pytest # isort: LOCAL import pyudev pytest_plugins = [ str("tests.plugins.fake_monitor"), 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.24.3/tests/plugins/000077500000000000000000000000001461746214500157515ustar00rootroot00000000000000pyudev-0.24.3/tests/plugins/__init__.py000066400000000000000000000017171461746214500200700ustar00rootroot00000000000000# -*- 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 """ pyudev-0.24.3/tests/plugins/fake_monitor.py000066400000000000000000000054371461746214500210110ustar00rootroot00000000000000# -*- 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. .. moduleauthor:: Sebastian Wiesner """ # isort: STDLIB import os from select import select # isort: THIRDPARTY import pytest class FakeMonitor: """ 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.getfixturevalue("fake_monitor_device")) pyudev-0.24.3/tests/plugins/mock_libudev.py000066400000000000000000000065221461746214500207730ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB from collections import namedtuple from contextlib import contextmanager from operator import attrgetter # isort: THIRDPARTY import pytest try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock Node = namedtuple("Node", "name value next") class LinkedList: """ 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_configure(): for f in EXPOSED_FUNCTIONS: setattr(pytest, f.__name__, f) pyudev-0.24.3/tests/plugins/travis.py000066400000000000000000000027301461746214500176350ustar00rootroot00000000000000# -*- 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. """ # isort: STDLIB import os # isort: THIRDPARTY 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_configure(): for f in EXPOSED_FUNCTIONS: setattr(pytest, f.__name__, f) 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.24.3/tests/test_core.py000066400000000000000000000065371461746214500166440ustar00rootroot00000000000000# -*- 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 # isort: STDLIB import random import syslog # isort: FIRSTPARTY from tests._constants import _UDEV_TEST from tests.utils import is_unicode_string # isort: LOCAL from pyudev import udev_version try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock 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: 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.24.3/tests/test_device.py000066400000000000000000000027661461746214500171530ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import gc from ._device_tests._attributes_tests import TestAttributes from ._device_tests._device_tests import TestDevice from ._device_tests._devices_tests import TestDevices # pylint: disable=unused-import from ._device_tests._tags_tests import TestTags # pylint: disable=unused-import 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.24.3/tests/test_discover.py000066400000000000000000000123751461746214500175270ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import os # isort: THIRDPARTY import pytest from hypothesis import assume, given, settings, strategies # isort: LOCAL import pyudev from pyudev import ( DeviceFileHypothesis, DeviceNameHypothesis, DeviceNumberHypothesis, DevicePathHypothesis, Discovery, ) _CONTEXT = pyudev.Context() _DEVICES = [d for d in _CONTEXT.list_devices()] NUM_TESTS = 5 class TestUtilities: """ 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: """ 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)] @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.24.3/tests/test_enumerate.py000066400000000000000000000217571461746214500177020ustar00rootroot00000000000000# -*- 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 # isort: THIRDPARTY from hypothesis import given, settings, strategies from ._constants import ( _ATTRIBUTE_STRATEGY, _CONTEXT_STRATEGY, _MATCH_PROPERTY_STRATEGY, _SUBSYSTEM_STRATEGY, _SYSNAME_STRATEGY, _TAG_STRATEGY, _UDEV_TEST, device_strategy, ) from .utils import failed_health_check_wrapper try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock 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: """ Test the Enumerator class. """ @failed_health_check_wrapper @given(_CONTEXT_STRATEGY, _SUBSYSTEM_STRATEGY) @settings(max_examples=10) 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_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=1) 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, _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, device_strategy(filter_func=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 class TestEnumeratorMatchCombinations: """ Test combinations of matches. """ @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: """ 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.24.3/tests/test_monitor.py000066400000000000000000000300731461746214500173730ustar00rootroot00000000000000# -*- 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 # isort: STDLIB import random from contextlib import contextmanager from datetime import datetime, timedelta from select import select # isort: THIRDPARTY import pytest # isort: FIRSTPARTY from tests._constants import _UDEV_TEST from tests.utils.udev import DeviceDatabase # isort: LOCAL from pyudev import Devices, Monitor, MonitorObserver try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock # 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.getfixturevalue("context")) @pytest.fixture def fake_monitor_device(request): context = request.getfixturevalue("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: 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()``. In some version it changed and it now raises FileNotFoundError. """ with pytest.raises(Exception): 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) 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 class TestMonitorObserver: 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 pyudev-0.24.3/tests/test_observer.py000066400000000000000000000152601461746214500175340ustar00rootroot00000000000000# -*- 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 # isort: STDLIB import random # isort: THIRDPARTY import pytest # isort: FIRSTPARTY from tests.utils.udev import DeviceDatabase # isort: LOCAL from pyudev import Devices, Monitor try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock @pytest.fixture def monitor(request): return Monitor.from_netlink(request.getfixturevalue("context")) @pytest.fixture def fake_monitor_device(request): context = request.getfixturevalue("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: 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) 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 = [] pytest.importorskip("gi") self.glib = pytest.importorskip("gi.repository.GLib") def teardown(self): for source in self.event_sources: self.glib.source_remove(source) def create_observer(self, monitor): # isort: LOCAL 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): # isort: LOCAL from pyudev import wx self.observer = wx.MonitorObserver(monitor) def connect_signal(self, callback): # isort: LOCAL 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.24.3/tests/test_observer_deprecated.py000066400000000000000000000201661461746214500217150ustar00rootroot00000000000000# -*- 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 # isort: THIRDPARTY import pytest # isort: LOCAL from pyudev import Devices, Monitor try: # isort: STDLIB from unittest import mock except ImportError: # isort: THIRDPARTY import mock @pytest.fixture def monitor(request): return Monitor.from_netlink(request.getfixturevalue("context")) @pytest.fixture def fake_monitor_device(request): context = request.getfixturevalue("context") return Devices.from_path(context, "/devices/platform") ACTIONS = ("add", "remove", "change", "move") class DeprecatedObserverTestBase: 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) 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 = [] pytest.importorskip("gi") self.glib = pytest.importorskip("gi.repository.GLib") def teardown(self): for source in self.event_sources: self.glib.source_remove(source) def create_observer(self, monitor): # isort: LOCAL 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): # isort: LOCAL 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: # isort: LOCAL 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.24.3/tests/test_util.py000066400000000000000000000135261461746214500166650ustar00rootroot00000000000000# -*- 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 # isort: STDLIB import sys # isort: THIRDPARTY import pytest from hypothesis import given, settings, strategies # isort: LOCAL from pyudev import Context, _util from .utils import is_unicode_string try: # isort: STDLIB from unittest.mock import Mock except ImportError: # isort: THIRDPARTY from mock import Mock _CONTEXT = Context() 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 def test_ensure_byte_string_none(): with pytest.raises(AttributeError): _util.ensure_byte_string(None) 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 def test_ensure_unicode_string_none(): with pytest.raises(AttributeError): _util.ensure_unicode_string(None) 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 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) 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) def test_string_to_bool_true(): assert isinstance(_util.string_to_bool("1"), bool) assert _util.string_to_bool("1") def test_string_to_bool_false(): assert isinstance(_util.string_to_bool("0"), bool) assert not _util.string_to_bool("0") 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(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(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): # isort: STDLIB import os import select import signal 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 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.24.3/tests/utils/000077500000000000000000000000001461746214500154305ustar00rootroot00000000000000pyudev-0.24.3/tests/utils/__init__.py000066400000000000000000000020141461746214500175360ustar00rootroot00000000000000# -*- 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, is_unicode_string pyudev-0.24.3/tests/utils/misc.py000066400000000000000000000035441461746214500167430ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB from functools import wraps # isort: THIRDPARTY 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, 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: 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.24.3/tests/utils/udev.py000066400000000000000000000232451461746214500167530ustar00rootroot00000000000000# -*- 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 """ # isort: STDLIB import collections import errno import os import re import subprocess import sys class UDevAdm: """ 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: """ 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. """ device_node = self.device_node return 0 if device_node is None else os.stat(device_node).st_rdev class DeviceDatabase(collections.abc.Iterable, collections.abc.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