pax_global_header00006660000000000000000000000064147745601600014524gustar00rootroot0000000000000052 comment=d87ff13a7ed00e86467b162f8369559f22e50b8b python-sdbus-0.14.0/000077500000000000000000000000001477456016000142455ustar00rootroot00000000000000python-sdbus-0.14.0/.clang-format000066400000000000000000000001211477456016000166120ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: Chromium IndentWidth: 8 ColumnLimit: 160 python-sdbus-0.14.0/.github/000077500000000000000000000000001477456016000156055ustar00rootroot00000000000000python-sdbus-0.14.0/.github/FUNDING.yml000066400000000000000000000000461477456016000174220ustar00rootroot00000000000000patreon: igo95862 liberapay: igo95862 python-sdbus-0.14.0/.github/workflows/000077500000000000000000000000001477456016000176425ustar00rootroot00000000000000python-sdbus-0.14.0/.github/workflows/ubuntu_pypi_test.yml000066400000000000000000000015571477456016000240170ustar00rootroot00000000000000--- name: Install package from PyPI and run unit tests on Ubuntu 20.04 on: workflow_dispatch: inputs: pypi_version: description: "Version specifier to install from PyPI" jobs: run: name: Install from PyPI and run unit tests runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Install dependencies run: | sudo apt update sudo apt install python3-setuptools \ systemd dbus python3 python3-pip python3-jinja2 - name: Install package run: | sudo pip3 install "sdbus ${SDBUS_VERSION}" env: SDBUS_VERSION: ${{ inputs.pypi_version }} - name: List package run: | pip3 list | grep sdbus - name: Run unit tests run: | python3 -m unittest python-sdbus-0.14.0/.github/workflows/ubuntu_test.yml000066400000000000000000000047261477456016000227570ustar00rootroot00000000000000--- name: CI on: push: pull_request: workflow_dispatch: jobs: unlimited: name: Run build and unit tests. (unlimited API) runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Install dependencies run: | sudo apt update sudo apt install python3-setuptools python3-dev libsystemd-dev \ systemd dbus python3 gcc - name: Build extension run: | python3 setup.py build --build-lib build-lib - name: Run unit tests run: | PYTHONPATH=./build-lib python3 -m unittest --verbose limited: name: Run build and unit tests. (limited API) runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Install dependencies run: | sudo apt update sudo apt install python3-setuptools python3-dev libsystemd-dev \ systemd dbus python3 gcc pkg-config - name: Build extension env: PYTHON_SDBUS_USE_LIMITED_API: "1" run: | python3 setup.py build --build-lib build-lib - name: Run unit tests run: | PYTHONPATH=./build-lib python3 -m unittest --verbose lint: name: Run linters on the code runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Install dependencies run: | sudo apt update sudo apt install python3 python3-pip ninja-build python -m venv --system-site-packages venv ./venv/bin/pip install --upgrade \ mypy isort flake8 pyflakes pycodestyle \ jinja2 'Sphinx<8.0' types-setuptools meson - name: Run linters run: | export PATH="$(readlink -f ./venv/bin):${PATH}" meson setup build meson compile -C build lint-python alpine: name: Alpine Linux test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Build Alpine container run: | podman build --tag alpine-ci -f ./test/containers/Containerfile-alpine . - name: Test unlimited API run: | podman run --rm alpine-ci - name: Test limited API run: | podman run --env PYTHON_SDBUS_USE_LIMITED_API=1 --rm alpine-ci python-sdbus-0.14.0/.gitignore000066400000000000000000000016451477456016000162430ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of py_sd_bus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA .mypy_cache .vscode *.pyc __pycache__ build dev* strace*.txt *.so .cache wheels *.kate-swp python-sdbus-0.14.0/.python-version000066400000000000000000000000151477456016000172460ustar00rootroot000000000000003.8.7 system python-sdbus-0.14.0/.readthedocs.yaml000066400000000000000000000002571477456016000175000ustar00rootroot00000000000000--- version: 2 build: os: "ubuntu-22.04" tools: python: "3.9" sphinx: configuration: "docs/conf.py" python: install: - requirements: docs/requirements.txt python-sdbus-0.14.0/CHANGELOG.md000066400000000000000000000274001477456016000160610ustar00rootroot00000000000000## 0.14.0 ### Minimum requirements raised * Python 3.9 or higher. For binary PyPI wheel: * glibc 2.28 or higher. (Debian 10+, Ubuntu 18.10+, CentOS/RHEL 8+) * pip 20.3 or higher. * Added 32 bit ARM (`armv7l`) architecture wheel. ### Default bus changes Previously the default bus always used the context-local variables to store the reference to the current default bus. As it turned out the context tends to be changed a lot which resulted in new buses being opened multiple times. (reported by @wes8ty) To avoid this the default bus was changed to be thread-local. `set_default_bus` will now set the thread-local default bus. A new function `set_context_default_bus` was added to set the context-local bus. The `get_default_bus` will return the context-local bus if set or thread-local otherwise. If no default bus has been set a new thread-local bus will be initialized and set. ### Code generator * Code generator will now add manual D-Bus member name override where automatic snake_case to CamelCase does not result in the original member name. This applies to when member renaming options were used. (reported by @nicomuns) * Generated code will now use Python 3.9 built-in collections type hints. (`typing.List[str]` -> `list[str]`) * Fixed blocking generated code adding unexpected `result_args_names` keyword. (reported by @christophehenry) ### Features * All `sdbus.utils.parse` functions can now accept the blocking interfaces. (requested by @christophehenry) * Added boolean `use_interface_subsets` option to `sdbus.utils.parse` functions. When enabled the subset of interfaces will be considered a valid match. (requested by @christophehenry) ### Fixes * Fixed exceptions mapped by `map_exception_to_dbus_error` not being translated from Python to D-Bus errors. This means the Python built-in exceptions will now be properly returned as D-Bus errors when raised in exported object callback. The built-in exceptions translating as added back in version 0.10.0 but probably never worked correctly. (reported by @arkq) * Fixed not being able to export interfaces with no implemented D-Bus members. This also means `export_to_dbus` will only access D-Bus related attributes avoiding triggering unrelated `@property` methods. * Renamed certain internal classes from `Binded` to `Bound` and from `DbusSomething` to `DbusMember`. (reported by @souliane, implemented by @dragomirecky) ## 0.13.0 ### Code generator improvements * Added interface and member renaming CLI options. `--select-interface`, `--select-method`, `--select-property` and `--select-signal` will select a particular interface or member and `--set-name` will set the selected interface or member Python name. * Fix generated D-Bus properties not using emits changed flag by default. * Fix generated D-Bus methods not using unprivileged flag by default. (reported by @damienklotz77) * Generated methods and signals will now have result argument names set which will be shown in the introspection. (requested by @colazzo) ### New `sdbus.utils.inspect` submodule Contains inspection utilities. Current only provides the `inspect_dbus_path` function which will return the D-Bus path of either proxy or exported object. (requested by ) ### New `sdbus.utils.parse` submodule The existing `parse_properties_changed`, `parse_interfaces_added`, `parse_interfaces_removed` and `parse_get_managed_objects` have been moved from from `sdbus.utils` to `sdbus.utils.parse`. For backwards compatibility `sdbus.utils` re-exports those functions but no new exports will be added to it. ### Fixes * Fix bus timeouts not being processed on time. (requested by @ofacklam) ## 0.12.0 No changes since 0.12.RC1. ## 0.12.RC1 This version significantly reworked the internal undocumented classes and functions. If you used the undocumented API you would probably need to adjust your code. Type checker like `mypy` can be very useful for this. ### Features: * `@setter_private` can now be used in overrides. * Added `assertDbusSignalEmits` method to `IsolatedDbusTestCase`. Can be used to assert that a D-Bus signal was emitted inside the `async with` block. * Added `sdbus.utils.parse_get_managed_objects` function. Can be used to parse the ObjectManager's `get_managed_objects` method data to classes and Python attribute names. * Added a handle that is returned by `export_to_dbus` and `export_with_manager` methods. This handle can be used to explicitly control when object is accessible from D-Bus. (requested by @dragomirecky) ### Fixes: * Fixed async D-Bus properties not having a proper generic typing. (reported by @ValdezFOmar) * Fixed build not working when systemd has a minor version suffix. * Fixed being unable to name arguments in D-Bus introspection when method has no return arguments. (reported by @colazzo) * Fixed serving D-Bus methods that return a single struct. (reported by @colazzo) * Fixed sending extremely large D-Bus messages getting stuck. (reported by @colazzo) ## 0.11.1 ### Features: * Improved interface generator handling of multiple uppercase letters sequences. For example, `ACTIVATE_CONNECTION` would before be converted to `a_c_t_i_v_a_t_e__c_o_n_n_e_c_t_i_o_n` and after to `activate_connection`. (reported by @bhattarabi) * Improved python formatting generated by interface code generator. * Added option `--block` to generate blocking interface code. (requested by @zhanglongqi and @MathisMARION) ### Fixes: * Fixed docstrings still being present even if python was configured with `--without-doc-strings`. * Fixed interface generator crashing when a rare write-only property is encountered. (reported by @gotthardp) * Fixed async interfaces iterating over all members during initialization. (reported by @gotthardp) * Fixed `TypeError: Dbus type '\x00' is unknown` being raised when trying to read from a message more than one time. (reported by @IB1387 and @asmello) * Fixed missing class body when generating code for interface without members. ## 0.11.0 ### Features: * Added support for `None` signals without data. * Added boolean flags for the name request functions which can be used to specify replacements or queueing. * Added `sdbus.utils.parse_properties_changed` helper function. Parses signal data to python member names and values. * Added `sdbus.utils.parse_interfaces_added` helper function. Parses signal data to path, python class and python member names and values. * Added `sdbus.utils.parse_interfaces_removed` helper function. Parses signal data to path and python class. * Added `setter_private` decorator to async properties. Private setter can only be called locally but to D-Bus property will appear as read only. * Added new exceptions for when D-Bus name requests fail. * `SdBusRequestNameExistsError`: Someone already owns name. * `SdBusRequestNameAlreadyOwnerError`: Caller already owns name. * `SdBusRequestNameInQueueError`: Name request queued up. ### Deprecations: * Moved all exceptions to `sdbus.exceptions` module. For backwards compatibility old exceptions will be available from the root module until the version `1.0.0`. ### Fixes: * Fixed autodoc adding `dbus_method` to dbus methods names * Fix async D-Bus name requests not raising appropriate exceptions. * Fixed `request_default_bus_name` being an async function. For backwards compatibility it returns an awaitable that raises a warning. ## 0.10.2 ### Features: * Added `on_unknown_member` option to the `properties_get_all_dict` method. Specifies the action on what to do with unknown property. (`"error"` (default), `"ignore"`, `"reuse"`) ### Fixes: * Fixed autodoc regressions introduced in `0.10.1`. Properties and signals headers have been redesigned. * Fixed PropertiesChanged signal emitting only the newest object path. ## 0.10.1 ### Features: * Added `catch_anywhere` method to dbus signals. Creates an async iterator which yields object path that emitted signal and signal data. Can be called from class but requires explicit service name in that case. * Added `properties_get_all_dict()` method to `DbusInterfaceCommonAsync` and `DbusInterfaceCommon` classes. Retrieves all D-Bus object properties as a dictionary where keys are member names translated to Python names and values are property values. ## 0.10.0 ### Features: * **Mapped all built-in Python exceptions to D-Bus errors.** * **Default bus connection now uses context/thread-local storage.** This is potentially breaking in code that used bus in a different threads or contexts. * Added `map_exception_to_dbus_error` which lets mapping of any exception to D-Bus error name. * D-Bus interfaces and member names are now verified before exporting to D-Bus. A helpful error message will be returned if verification fails. * Added ability to export and track objects with ObjectManager. * Added `sdbus.unittest.IsolatedDbusTestCase` which is a test case that runs on a separated D-Bus instance. Requires `dbus-daemon` command be installed. * Allow replacing the default bus. Changing default bus will not have effect on existing objects which will continue to use the old bus. ### Fixes: * Fixed non-mapped errors in methods called from D-Bus not returning generic error. * Fixed errors in properties always returning Access Denied instead of specific or generic D-Bus errors. * Fixed `str` and `int` subclasses not being accepted on fast API. * Fixes trying to process a disconnected bus and causing high CPU usage. * Marked autodoc extension safe for parallel reading and writing. ## 0.9.0 * **pkg-config is now required** when building from source. * **Added support for Alpine Linux** and any other distros using elogind instead of systemd. * Improved PropertiesChanged signal emissions for python objects. * Fixed python D-Bus methods that return single struct. ## 0.8.5 * Fixed missing header file from the source package. ## 0.8.4 * Deprecated `DbusInterfaceCommonAsync._connect` and `DbusInterfaceCommonAsync.new_connect` in favor of `DbusInterfaceCommonAsync._proxify` and `DbusInterfaceCommonAsync.new_proxy` respectively. ## 0.8.3 ### Features: * Added `gen-from-connection` command to module that generates interface classes from run-time introspection. Takes service name and one or more object paths as arguments. Outputs to stdout. * Added `sd_bus_open_system_remote` call that opens a remote system bus through SSH. * Added `sd_bus_open_system_machine` and `sd_bus_open_user_machine` calls that open a bus connection inside systemd-nspawn containers. ### Fixes: * Typing stub will raise an exception when called in case the C module failed to load. This should reduce crypting errors in case the module failure. * Interface generator now skips standard interfaces such as `org.freedesktop.DBus.Introspectable` ## 0.8.2 * Added limited API module. This has advantage of working on multiple Python versions but 5% performance penalty. PyPI will probably use the limited API module. * Fixed any libsystemd errors causing a segmentation fault. ## 0.8.1 * Fixed unmapped errors not being raised properly. ## 0.8.0 * Added binary packages to PyPI. No more issues with libsystem versions. * **BREAKING** Moved proxies from `sdbus.proxies`, `sdbus.async_proxies` to `sdbus_async` and `sdbus_block`. * Added `org.freedesktop.DBus.ObjectManager` interface and a base interface with it. * Added `org.freedesktop.DBus.Properties` interface to base interface. * Added [autodoc extension](https://python-sdbus.readthedocs.io/en/latest/autodoc.html). * Added [interface code generator](https://python-sdbus.readthedocs.io/en/latest/code_generator.html). * Added `request_default_bus_name` to request name on default bus in blocking order. * Added `SdBus.request_name` to requests name in blocking order. Useful to initialize the daemons. * Fixed any initialized dbus objects never being deleted even if there were no more references to them. python-sdbus-0.14.0/COPYING000066400000000000000000000432541477456016000153100ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this License along with the Program. 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 Program or any portion of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 Program, 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 Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) 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; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, 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 executable. However, as a special exception, the source code 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. If distribution of executable or 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 counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. 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 Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program 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 to this License. 7. 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 Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program 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 Program. 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. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program 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. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program 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 Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, 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 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. 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 program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. python-sdbus-0.14.0/COPYING.LESSER000066400000000000000000000636421477456016000163070ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, 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 Street, 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! python-sdbus-0.14.0/DEPRECATIONS.md000066400000000000000000000015711477456016000164530ustar00rootroot00000000000000# Deprecation information ## Awaiting on `request_default_bus_name` By mistake `request_default_bus_name` was made in to async function even though it was never documented to be one. It is now a blocking function but returns an awaitable for backwards compatibility. * **Since**: 0.11.0 * **Warning**: 0.11.0 * **Removed**: 0.14.0 ## Importing exceptions from `sdbus` module All exceptions have been moved to `sdbus.exceptions` to clean up imports. * **Since**: 0.11.0 * **Warning**: Not possible? * **Removed**: 1.0.0 ## `_connect` and `new_connect` of the `DbusInterfaceCommonAsync` class Replaced with equivalent `_proxify` and `new_proxy`. * **Since**: 0.8.4 * **Warning**: 0.9.0 * **Removed**: 1.0.0 ## `start_serving` of the `DbusInterfaceCommonAsync` class Replaced with equivalent but sync `export_to_dbus`. * **Since**: 0.7.6 * **Warning**: 0.7.6 * **Removed**: 1.0.0 python-sdbus-0.14.0/PACKAGING.md000066400000000000000000000017111477456016000160530ustar00rootroot00000000000000## Compile options python-sdbus has several compiling options which can be passed to setup.py script using environment variables. ### PYTHON_SDBUS_USE_IGNORE_SYSTEMD_VERSION By default setup.py will try to figure out the libsystemd version using the `pkg-config` command to disable certain features if the libsystemd version is too low. If you want to skip the check and enable all features set this variable `1`. ### PYTHON_SDBUS_USE_STATIC_LINK Link statically against libsystemd and libcap. This is mainly used for PyPI package. Set this variable to `1` if you want to enable static linking. ### PYTHON_SDBUS_USE_LIMITED_API Use the [stable CPython API](https://docs.python.org/3/c-api/stable.html). This will make the python package forward compatible with future python versions without recompiling. The minimum python version will still be 3.7. Stable API has around 5% lower performance. Set this variable to `1` if you want to enable use of limited API. python-sdbus-0.14.0/README.md000066400000000000000000000135561477456016000155360ustar00rootroot00000000000000[![CodeQL](https://github.com/python-sdbus/python-sdbus/actions/workflows/codeql.yml/badge.svg)](https://github.com/python-sdbus/python-sdbus/actions/workflows/codeql.yml) [![Documentation Status](https://readthedocs.org/projects/python-sdbus/badge/?version=latest)](https://python-sdbus.readthedocs.io/en/latest/?badge=latest) # Modern Python library for D-Bus Packaging status Features: * Asyncio and blocking calls. * Type hints. (`mypy --strict` compatible) * No Python 2 legacy. * Based on fast sd-bus from systemd. (also supports elogind) * Unified client/server interface classes. Write interface once! * D-Bus methods can have keyword and default arguments. See the [documentation](https://python-sdbus.readthedocs.io/en/latest/index.html) for tutorial and API reference. ### List of implemented interfaces * D-Bus (built-in) * [Freedesktop Notifications](https://github.com/python-sdbus/python-sdbus-notifications) * [Network Manager](https://github.com/python-sdbus/python-sdbus-networkmanager) * [Freedesktop Secrets](https://github.com/python-sdbus/python-sdbus-secrets) More incoming. (systemd, Bluez, screen saver... ) ### Community interfaces * [systemd](https://github.com/bernhardkaindl/python-sdbus-systemd) (by [@bernhardkaindl](https://github.com/bernhardkaindl)) * [modemmanager](https://github.com/zhanglongqi/python-sdbus-modemmanager) (by [@zhanglongqi](https://github.com/zhanglongqi)) ## Stability Python-sdbus is under development and its API is not stable. Generally anything documented in the official documentation is considered stable but might be deprecated. Using deprecated feature will raise a warning and the feature will be eventually removed. See the [deprecations list](DEPRECATIONS.md). If there is a feature that is not documented but you would like to use please open a new issue. ## Requirements ### Binary package from PyPI * Python 3.9 or higher. * `x86_64`, `aarch64` or `armv7l` architecture. * glibc 2.28 or higher. (Debian 10+, Ubuntu 18.10+, CentOS/RHEL 8+) * pip 20.3 or higher. `libsystemd` is statically linked and is not required to be installed on the system. Pass `--only-binary ':all:'` to pip to ensure that it installs binary package. `i686`, `ppc64le` and `s390x` can be supported if there is a demand. Please open an issue if you are interested in those platforms. ### Source package or compiling from source * Python 3.9 or higher. * Python headers. (`python3-dev` package on ubuntu) * GCC. * libsystemd or libelogind * libsystemd headers. (`libsystemd-dev` package on ubuntu) * Python setuptools. * pkg-config Systemd version should be higher than 246. ### Optional dependencies * Jinja2 for code generator. * Sphinx for autodoc. ## Installation ### PyPI URL: https://pypi.org/project/sdbus/ `pip install --only-binary ':all:' sdbus` ### AUR URL: https://aur.archlinux.org/packages/python-sdbus-git/ ## Example code Interface `example_interface.py` file: ```python from sdbus import (DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, dbus_signal_async) # This is file only contains interface definition for easy import # in server and client files class ExampleInterface( DbusInterfaceCommonAsync, interface_name='org.example.interface' ): @dbus_method_async( input_signature='s', result_signature='s', ) async def upper(self, string: str) -> str: return string.upper() @dbus_property_async( property_signature='s', ) def hello_world(self) -> str: return 'Hello, World!' @dbus_signal_async( signal_signature='i' ) def clock(self) -> int: raise NotImplementedError ``` Server `example_server.py` file: ```python from asyncio import new_event_loop, sleep from random import randint from time import time from example_interface import ExampleInterface from sdbus import request_default_bus_name_async loop = new_event_loop() export_object = ExampleInterface() async def clock() -> None: """ This coroutine will sleep a random time and emit a signal with current clock """ while True: await sleep(randint(2, 7)) # Sleep a random time current_time = int(time()) # The interface we defined uses integers export_object.clock.emit(current_time) async def startup() -> None: """Perform async startup actions""" # Acquire a known name on the bus # Clients will use that name to address this server await request_default_bus_name_async('org.example.test') # Export the object to D-Bus export_object.export_to_dbus('/') loop.run_until_complete(startup()) task_clock = loop.create_task(clock()) loop.run_forever() ``` Client `example_client.py` file: ```python from asyncio import new_event_loop from example_interface import ExampleInterface # Create a new proxied object example_object = ExampleInterface.new_proxy('org.example.test', '/') async def print_clock() -> None: # Use async for loop to print clock signals we receive async for x in example_object.clock: print('Got clock: ', x) async def call_upper() -> None: s = 'test string' s_after = await example_object.upper(s) print('Initial string: ', s) print('After call: ', s_after) async def get_hello_world() -> None: print('Remote property: ', await example_object.hello_world) loop = new_event_loop() # Always bind your tasks to a variable task_upper = loop.create_task(call_upper()) task_clock = loop.create_task(print_clock()) task_hello_world = loop.create_task(get_hello_world()) loop.run_forever() ``` ## License Python-sdbus is licensed under [LGPL-2.1-or-later](https://spdx.org/licenses/LGPL-2.1-or-later.html). The LGPL license is an extension of GPL license therefore both licenses' texts are required. python-sdbus-0.14.0/docs/000077500000000000000000000000001477456016000151755ustar00rootroot00000000000000python-sdbus-0.14.0/docs/api_index.rst000066400000000000000000000054201477456016000176700ustar00rootroot00000000000000API Index ============================ .. py:module:: sdbus Common: ++++++++++++++++++++++++++ :py:func:`get_default_bus` :py:func:`request_default_bus_name_async` :py:func:`set_default_bus` :py:func:`decode_object_path` :py:func:`encode_object_path` :py:func:`sd_bus_open_system` :py:func:`sd_bus_open_user` :py:obj:`DbusDeprecatedFlag` :py:obj:`DbusHiddenFlag` :py:obj:`DbusNoReplyFlag` :py:obj:`DbusPropertyConstFlag` :py:obj:`DbusPropertyEmitsChangeFlag` :py:obj:`DbusPropertyEmitsInvalidationFlag` :py:obj:`DbusPropertyExplicitFlag` :py:obj:`DbusSensitiveFlag` :py:obj:`DbusUnprivilegedFlag` Asyncio: ++++++++++++++++++++++++++ :py:class:`DbusInterfaceCommonAsync` :py:func:`dbus_method_async` :py:func:`dbus_method_async_override` :py:func:`dbus_property_async` :py:func:`dbus_property_async_override` :py:func:`dbus_signal_async` Blocking: ++++++++++++++++++++++++++ :py:class:`DbusInterfaceCommon` :py:func:`dbus_method` :py:func:`dbus_property` Exceptions: ++++++++++++++++++++++++++ :py:exc:`exceptions.DbusAccessDeniedError` :py:exc:`exceptions.DbusAccessDeniedError` :py:exc:`exceptions.DbusAddressInUseError` :py:exc:`exceptions.DbusAuthFailedError` :py:exc:`exceptions.DbusBadAddressError` :py:exc:`exceptions.DbusDisconnectedError` :py:exc:`exceptions.DbusFailedError` :py:exc:`exceptions.DbusFileExistsError` :py:exc:`exceptions.DbusFileNotFoundError` :py:exc:`exceptions.DbusInconsistentMessageError` :py:exc:`exceptions.DbusInteractiveAuthorizationRequiredError` :py:exc:`exceptions.DbusInvalidArgsError` :py:exc:`exceptions.DbusInvalidFileContentError` :py:exc:`exceptions.DbusInvalidSignatureError` :py:exc:`exceptions.DbusIOError` :py:exc:`exceptions.DbusLimitsExceededError` :py:exc:`exceptions.DbusMatchRuleInvalidError` :py:exc:`exceptions.DbusMatchRuleNotFound` :py:exc:`exceptions.DbusNameHasNoOwnerError` :py:exc:`exceptions.DbusNoMemoryError` :py:exc:`exceptions.DbusNoNetworkError` :py:exc:`exceptions.DbusNoReplyError` :py:exc:`exceptions.DbusNoServerError` :py:exc:`exceptions.DbusNotSupportedError` :py:exc:`exceptions.DbusPropertyReadOnlyError` :py:exc:`exceptions.DbusServiceUnknownError` :py:exc:`exceptions.DbusTimeoutError` :py:exc:`exceptions.DbusUnixProcessIdUnknownError` :py:exc:`exceptions.DbusUnknownInterfaceError` :py:exc:`exceptions.DbusUnknownMethodError` :py:exc:`exceptions.DbusUnknownObjectError` :py:exc:`exceptions.DbusUnknownPropertyError` :py:exc:`exceptions.SdBusBaseError` :py:exc:`exceptions.SdBusLibraryError` :py:exc:`exceptions.SdBusUnmappedMessageError` :py:func:`exceptions.map_exception_to_dbus_error` :py:exc:`exceptions.SdBusRequestNameError` :py:exc:`exceptions.SdBusRequestNameInQueueError` :py:exc:`exceptions.SdBusRequestNameExistsError` :py:exc:`exceptions.SdBusRequestNameAlreadyOwnerError` python-sdbus-0.14.0/docs/asyncio_api.rst000066400000000000000000000512221477456016000202270ustar00rootroot00000000000000Asyncio API ============ Classes ++++++++++++++++++++ .. py:currentmodule:: sdbus .. py:class:: DbusInterfaceCommonAsync(interface_name) D-Bus async interface class. D-Bus methods and properties should be defined using :py:func:`dbus_property_async`, :py:func:`dbus_signal_async`, and :py:func:`dbus_method_async` decorators. .. note:: Don't forget to call ``super().__init__()`` in derived classes init calls as it sets up important attributes. :param str interface_name: Sets the D-Bus interface name that will be used for all properties, methods and signals defined in the body of the class. :param bool serving_enabled: If set to :py:obj:`True` the interface will not be served on D-Bus. Mostly used for interfaces that sd-bus already provides such as ``org.freedesktop.DBus.Peer``. .. py:method:: dbus_ping() :async: Pings the remote service using D-Bus. Useful to test if connection or remote service is alive. .. warning:: This method is ignores the particular object path meaning it can NOT be used to test if object exist. .. py:method:: dbus_machine_id() :async: Returns the machine UUID of D-Bus the object is connected to. :return: machine UUID :rtype: str .. py:method:: dbus_introspect() :async: Get D-Bus introspection XML. It is users responsibility to parse that data. :return: string with introspection XML :rtype: str .. py:method:: properties_get_all_dict() :async: Get all object properties as a dictionary where keys are member names and values are properties values. Equivalent to ``GetAll`` method of the ``org.freedesktop.DBus.Properties`` interface but the member names are automatically translated to python names. (internally calls it for each interface used in class definition) :param str on_unknown_member: If an unknown D-Bus property was encountered either raise an ``"error"`` (default), ``"ignore"`` the property or ``"reuse"`` the D-Bus name for the member. :return: dictionary of properties :rtype: dict[str, Any] .. py:attribute:: properties_changed :type: tuple[str, dict[str, tuple[str, Any]], list[str]] Signal when one of the objects properties changes. :py:func:`sdbus.utils.parse.parse_properties_changed` can be used to transform this signal data in to an easier to work with dictionary. Signal data is: Interface name : str Name of the interface where property changed Changed properties : dict[str, tuple[str, Any]] Dictionary there keys are names of properties changed and values are variants of new value. Invalidated properties : list[str] List of property names changed but no new value had been provided .. py:method:: _proxify(bus, service_name, object_path) Begin proxying to a remote D-Bus object. :param str service_name: Remote object D-Bus connection name. For example, systemd uses ``org.freedesktop.systemd1`` :param str object_path: Remote object D-Bus path. Should be a forward slash separated path. Starting object is usually ``/``. Example: ``/org/freedesktop/systemd/unit/dbus_2eservice`` :param SdBus bus: Optional D-Bus connection object. If not passed the default D-Bus will be used. .. py:classmethod:: new_proxy(bus, service_name, object_path) Create new proxy object and bypass ``__init__``. :param str service_name: Remote object D-Bus connection name. For example, systemd uses ``org.freedesktop.systemd1`` :param str object_path: Remote object D-Bus path. Should be a forward slash separated path. Starting object is usually ``/``. Example: ``/org/freedesktop/systemd/unit/dbus_2eservice`` :param SdBus bus: Optional D-Bus connection object. If not passed the default D-Bus will be used. .. py:method:: export_to_dbus(object_path, bus) Object will appear and become callable on D-Bus. Returns a handle that can either be used as a context manager to remove the object from D-Bus or ``.stop()`` method of the handle can be called to remove object from D-Bus. Returns a handle that can be used to remove object from D-Bus by either using it as a context manager or by calling ``.stop()`` method of the handle. .. code-block:: python with dbus_object.export_to_dbus("/"): # dbus_object can be called from D-Bus inside this # with block. ... ... handle = dbus_object2.export_to_dbus("/") # dbus_object2 can be called from D-Bus between these statements handle.stop() ... dbus_object3.export_to_dbus("/") # dbus_object3 can be called from D-Bus until all references are # dropped. del dbus_object3 If the handle is discarded the object will remain exported until it gets deallocated. *Changed in version 0.12.0:* Added a handle return. :param str object_path: Object path that it will be available at. :param SdBus bus: Optional D-Bus connection object. If not passed the default D-Bus will be used. :return: Handle to control the export. .. py:class:: DbusObjectManagerInterfaceAsync(interface_name) This class is almost identical to :py:class:`DbusInterfaceCommonAsync` but implements `ObjectManager `_ interface. Example of serving objects with ObjectManager:: my_object_manager = DbusObjectManagerInterfaceAsync() my_object_manager.export_to_dbus('/object/manager') managed_object = DbusInterfaceCommonAsync() my_object_manager.export_with_manager('/object/manager/example', managed_object) .. py:method:: get_managed_objects() :async: Get the objects this object manager in managing. :py:func:`sdbus.utils.parse.parse_get_managed_objects` can be used to make returned data easier to work with. :return: Triple nested dictionary that contains all the objects paths with their properties values. dict[ObjectPath, dict[InterfaceName, dict[PropertyName, PropertyValue]]] :rtype: dict[str, dict[str, dict[str, Any]]] .. py:attribute:: interfaces_added :type: tuple[str, dict[str, dict[str, Any]]] Signal when a new object is added or and existing object gains a new interface. :py:func:`sdbus.utils.parse.parse_interfaces_added` can be used to make signal data easier to work with. Signal data is: Object path : str Path to object that was added or modified. Object interfaces and properties : dict[str, dict[str, Any]]] dict[InterfaceName, dict[PropertyName, PropertyValue]] .. py:attribute:: interfaces_removed :type: tuple[str, list[str]] Signal when existing object or and interface of existing object is removed. :py:func:`sdbus.utils.parse.parse_interfaces_removed` can be used to make signal data easier to work with. Signal data is: Object path : str Path to object that was removed or modified. Interfaces list : list[str] Interfaces names that were removed. .. py:method:: export_with_manager(object_path, object_to_export, bus) Export object to D-Bus and emit a signal that it was added. ObjectManager must be exported first. Path should be a subpath of where ObjectManager was exported. Example, if ObjectManager exported to ``/object/manager``, the managed object can be exported at ``/object/manager/test``. ObjectManager will keep the reference to the object. Returns a handle that can be used to remove object from D-Bus and drop reference to it by either using it as a context manager or by calling ``.stop()`` method of the handle. Signal will be emitted once the object is stopped being exported. .. code-block:: python manager = DbusObjectManagerInterfaceAsync() manager.export_to_dbus('/object/manager') with manager.export_with_manager("/object/manager/example", dbus_object): # dbus_object can be called from D-Bus inside this # with block. ... # Removed signal will be emitted once the with block exits ... handle = manager.export_with_manager("/object/manager/example", dbus_object2) # dbus_object2 can be called from D-Bus between these statements handle.stop() # Removed signal will be emitted once the .stop() method is called If the handle is discarded the object will remain exported until it gets removed from manager with :py:meth:`remove_managed_object` and the object gets deallocated. *Changed in version 0.12.0:* Added a handle return. :param str object_path: Object path that it will be available at. :param DbusInterfaceCommonAsync object_to_export: Object to export to D-Bus. :param SdBus bus: Optional D-Bus connection object. If not passed the default D-Bus will be used. :raises RuntimeError: ObjectManager was not exported. :return: Handle to control the export. .. py:method:: remove_managed_object(managed_object) Emit signal that object was removed. Releases reference to the object. .. caution:: The object will still be accessible over D-Bus until all references to it will be removed. :param DbusInterfaceCommonAsync managed_object: Object to remove from ObjectManager. :raises RuntimeError: ObjectManager was not exported. :raises KeyError: Passed object is not managed by ObjectManager. Decorators ++++++++++++++++++++++++ .. py:decorator:: dbus_method_async([input_signature, [result_signature, [flags, [result_args_names, [input_args_names, [method_name]]]]]]) Define a method. Underlying function must be a coroutine function. :param str input_signature: D-Bus input signature. Defaults to "" meaning method takes no arguments. Required if you intend to connect to a remote object. :param str result_signature: D-Bus result signature. Defaults to "" meaning method returns empty reply on success. Required if you intend to serve the object. :param int flags: modifies behavior. No effect on remote connections. Defaults to 0 meaning no special behavior. See :ref:`dbus-flags` . :param Sequence[str] result_args_names: sequence of result argument names. These names will show up in introspection data but otherwise have no effect. Sequence can be list, tuple, etc... Number of elements in the sequence should match the number of result arguments otherwise :py:exc:`SdBusLibraryError` will be raised. Defaults to result arguments being nameless. :param Sequence[str] input_args_names: sequence of input argument names. These names will show up in introspection data but otherwise have no effect. Sequence can be list, tuple, etc... Number of elements in the sequence should match the number of result arguments otherwise :py:exc:`RuntimeError` will be raised. If ``result_args_names`` has been passed when Python function argument names will be used otherwise input arguments will be nameless :param str method_name: Force specific D-Bus method name instead of being based on Python function name. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_method_async class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.test' ): # Method that takes a string # and returns uppercase of that string @dbus_method_async( input_signature='s', result_signature='s', result_args_names=('uppercased', ) # This is optional but # makes arguments have names in # introspection data. ) async def upper(self, str_to_up: str) -> str: return str_to_up.upper() .. py:decorator:: dbus_property_async(property_signature, [flags, [property_name]]) Declare a D-Bus property. The underlying function has to be a regular ``def`` function. The property will be read-only or read/write based on if setter was declared. .. warning:: Properties are supposed to be lightweight to get or set. Make sure property getter or setter does not perform heavy IO or computation as that will block other methods or properties. :param str property_signature: Property D-Bus signature. Has to be a single type or container. :param int flags: modifies behavior. No effect on remote connections. Defaults to 0 meaning no special behavior. See :ref:`dbus-flags` . :param str property_name: Force specific property name instead of constructing it based on Python function name. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_property_async class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.test' ): def __init__(self) -> None: # This is just a generic init self.i = 12345 self.s = 'test' # Read only property. No setter defined. @dbus_property_async('i') def read_only_number(self) -> int: return self.i # Read/write property. First define getter. @dbus_property_async('s') def read_write_str(self) -> str: return self.s # Now create setter. Method name does not matter. @read_write_str.setter # Use the property setter method as decorator def read_write_str_setter(self, new_str: str) -> None: self.s = new_str .. py:class:: DbusPropertyAsync Properties have following methods: .. py:decoratormethod:: setter(set_function) Defines the setter function. This makes the property read/write instead of read-only. See example on how to use. .. py:decoratormethod:: setter_private(set_function) Defines the private setter function. The setter can be called locally but property will be read-only from D-Bus. Calling the setter locally will emit :py:attr:`properties_changed ` signal to D-Bus. *Changed in version 0.12.0:* can now be used in overrides. .. py:method:: get_async() :async: Get the property value. The property can also be directly ``await`` ed instead of calling this method. .. py:method:: set_async(new_value) :async: Set property value. .. py:decorator:: dbus_signal_async([signal_signature, [signal_args_names, [flags, [signal_name]]]]) Defines a D-Bus signal. Underlying function return type hint is used for signal type hints. :param str signal_signature: signal D-Bus signature. Defaults to empty signal. :param Sequence[str] signal_args_names: sequence of signal argument names. These names will show up in introspection data but otherwise have no effect. Sequence can be list, tuple, etc... Number of elements in the sequence should match the number of result arguments otherwise :py:exc:`RuntimeError` will be raised. Defaults to result arguments being nameless. :param int flags: modifies behavior. No effect on remote connections. Defaults to 0 meaning no special behavior. See :ref:`dbus-flags` . :param str signal_name: Forces specific signal name instead of being based on Python function name. Example:: from sdbus import DbusInterfaceCommonAsync, dbus_signal_async class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.signal' ): @dbus_signal_async('s') def name_changed(self) -> str: raise NotImplementedError .. py:class:: DbusSignalAsync Signals have following methods: .. py:method:: catch() Catch D-Bus signals using the async generator for loop: ``async for x in something.some_signal.catch():`` This is main way to await for new events. Both remote and local objects operate the same way. Signal objects can also be async iterated directly: ``async for x in something.some_signal`` .. py:method:: catch_anywhere(service_name, bus) Catch signal independent of path. Yields tuple of path of the object that emitted signal and signal data. ``async for path, data in something.some_signal.catch_anywhere():`` This method can be called from both an proxy object and class. However, it cannot be called on local objects and will raise ``NotImplementedError``. :param str service_name: Service name of which signals belong to. Required if called from class. When called from proxy object the service name of the proxy will be used. :param str bus: Optional D-Bus connection object. If not passed when called from proxy the bus connected to proxy will be used or when called from class default bus will be used. .. py:method:: emit(args) Emit a new signal with *args* data. .. py:decorator:: dbus_method_async_override() Override the method. Method name should match the super class method name that you want to override. New method should take same arguments. You **must** add round brackets to decorator. Example: :: from sdbus import (DbusInterfaceCommonAsync, dbus_method_async dbus_method_async_override) class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.test' ): # Original call @dbus_method_async('s', 's') async def upper(self, str_to_up: str) -> str: return str_to_up.upper() class ExampleOverride(ExampleInterface): @dbus_method_async_override() async def upper(self, str_to_up: str) -> str: return 'Upper: ' + str_to_up.upper() .. py:decorator:: dbus_property_async_override() Override property. You **must** add round brackets to decorator. Example: :: from sdbus import (DbusInterfaceCommonAsync, dbus_property_async dbus_property_async_override) class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.test' ): def __init__(self) -> None: self.s = 'aaaaaaaaa' # Original property @dbus_property_async('s') def str_prop(self) -> str: return self.s @str_prop.setter def str_prop_setter(self, new_s: str) -> None: self.s = new_s class ExampleOverride(ExampleInterface): @dbus_property_async_override() def str_prop(self) -> str: return 'Test property' + self.s # Setter needs to be decorated again to override @str_prop.setter def str_prop_setter(self, new_s: str) -> None: self.s = new_s.upper() python-sdbus-0.14.0/docs/asyncio_deep.rst000066400000000000000000000016721477456016000203770ustar00rootroot00000000000000Asyncio advanced topics +++++++++++++++++++++++++ .. py:currentmodule:: sdbus Signals without data ^^^^^^^^^^^^^^^^^^^^ D-Bus allows signals to not carry any data. Such signals have the type signature of ``""``. (empty string) To emit such signals the :py:meth:`emit ` must be explicitly called with ``None``. Example of an empty signal:: from asyncio import new_event_loop from sdbus import DbusInterfaceCommonAsync, dbus_signal_async class ExampleInterface( DbusInterfaceCommonAsync, interface_name="org.example.signal" ): @dbus_signal_async("") def name_invalidated(self) -> None: raise NotImplementedError test_object = ExampleInterface() async def emit_empty_signal() -> None: test_object.export_to_dbus("/") test_object.name_invalidated.emit(None) loop = new_event_loop() loop.run_until_complete(emit_empty_signal()) python-sdbus-0.14.0/docs/asyncio_quick.rst000066400000000000000000000246361477456016000206030ustar00rootroot00000000000000Asyncio quick start +++++++++++++++++++++ .. py:currentmodule:: sdbus Interface classes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python-sdbus works by declaring interface classes. Interface classes for async IO should be derived from :py:class:`DbusInterfaceCommonAsync`. The class constructor takes ``interface_name`` keyword to determine the D-Bus interface name for all D-Bus elements declared in the class body. Example: :: from sdbus import DbusInterfaceCommonAsync class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.myinterface' ): ... Interface class body should contain the definitions of methods, properties and signals using decorators such as :py:func:`dbus_method_async`, :py:func:`dbus_property_async` and :py:func:`dbus_signal_async`. Example: :: from sdbus import (DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, dbus_signal_async) class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.myinterface' ): # Method that takes an integer and multiplies it by 2 @dbus_method_async('i', 'i') async def double_int(self, an_int: int) -> None: return an_int * 2 # Read only property of str @dbus_property_async('s') def read_string(self) -> int: return 'Test' # Signal with a list of strings @dbus_signal_async('as') def str_signal(self) -> list[str]: raise NotImplementedError Initiating proxy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :py:class:`DbusInterfaceCommonAsync` provides two methods for proxying remote objects. :py:meth:`DbusInterfaceCommonAsync.new_proxy` class method bypasses the class ``__init__`` and returns proxy object. :py:meth:`DbusInterfaceCommonAsync._proxify` should be used inside the ``__init__`` methods if your class is a proxy only. Recommended to create proxy classes that a subclass of the interface: :: from sdbus import DbusInterfaceCommonAsync class ExampleInterface(...): # Some interface class ... class ExampleClient(ExampleInterface): def __init__(self) -> None: # Your client init can proxy to any object based on passed arguments. self._proxify('org.example.test', '/') .. note:: Successfully initiating a proxy object does NOT guarantee that the D-Bus object exists. Serving objects ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :py:meth:`DbusInterfaceCommonAsync.export_to_dbus` method will export the object to the D-Bus. After calling it the object becomes visible on D-Bus for other processes to call. Example using ExampleInterface from before: :: from sdbus import request_default_bus_name_async loop = get_event_loop() i = ExampleInterface() async def start() -> None: # Acquire a name on the bus await request_default_bus_name_async('org.example.test') # Start serving at / path i.export_to_dbus('/') loop.run_until_complete(start()) loop.run_forever() Connection transparency ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The interface objects are designed to be transparent to their connection status. This means if the object not proxied to remote the calls to decorated methods will still work in the local scope. This is the call to local object: :: i = ExampleInterface() async def test() -> None: print(await i.double_int(5)) # Will print 10 This is a call to remote object at ``'org.example.test'`` service name and ``'/'`` path: :: i = ExampleInterface.new_proxy('org.example.test', '/') async def test() -> None: print(await i.double_int(5)) # Will print 10 Methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Methods are async function calls wrapped with :py:func:`dbus_method_async` decorator. (see the API reference for decorator parameters) Methods have to be async function, otherwise :py:exc:`AssertionError` will be raised. While method calls are async there is a inherit timeout timer for any method call. To return an error to caller you need to raise exception which has a :py:exc:`.DbusFailedError` as base. Regular exceptions will not propagate. See :doc:`/exceptions`. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_method_async class ExampleInterface(...): ... # Body of some class # Method that takes a string # and returns uppercase of that string @dbus_method_async( input_signature='s', result_signature='s', result_args_names=('uppercased', ) # This is optional but # makes arguments have names in # introspection data. ) async def upper(self, str_to_up: str) -> str: return str_to_up.upper() Methods behave exact same way as Python methods would: :: print(await example_object.upper('test')) # prints TEST Properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Properties are a single value that can be read and write. To declare a read only property you need to decorate a regular function with :py:func:`dbus_property_async` decorator. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_property_async class ExampleInterface(...): ... # Body of some class # Read only property. No setter defined. @dbus_property_async('i') def read_only_number(self) -> int: return 10 To create a read/write property you need to decorate the setter function with the :py:obj:`setter` attribute of your getter function. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_property_async class ExampleInterface(...): ... # Body of some class # Read/write property. First define getter. @dbus_property_async('s') def read_write_str(self) -> str: return self.s # Now create setter. Method name does not matter. @read_write_str.setter # Use the property setter method as decorator def read_write_str_setter(self, new_str: str) -> None: self.s = new_str Properties are supposed to be lightweight. Make sure you don't block event loop with getter or setter. Async properties do not behave the same way as :py:func:`property` decorator does. To get the value of the property you can either directly ``await`` on property or use :py:meth:`get_async` method. (also need to be awaited) To set property use :py:meth:`set_async` method. Example: :: ... # Somewhere in async function # Assume we have example_object of class defined above print(await example_object.read_write_str) # Print the value of read_write_str ... # Set read_write_str to new value await example_object.read_write_str.set_async('test') Signals ^^^^^^^^^^^^^^^^^^^^^^^^^^^ To define a D-Bus signal wrap a function with :py:func:`dbus_signal_async` decorator. The function is only used for type hints information. It is recommended to just put ``raise NotImplementedError`` in to the body of the function. Example: :: from sdbus import DbusInterfaceCommonAsync, dbus_signal_async class ExampleInterface(...): ... # Body of some class @dbus_signal_async('s') def name_changed(self) -> str: raise NotImplementedError To catch a signal use ``async for`` loop: :: async for x in example_object.name_changed: print(x) .. warning:: If you are creating an asyncio task to listen on signals make sure to bind it to a variable and keep it referenced otherwise garbage collector will destroy your task. A signal can be emitted with :py:meth:`emit ` method. Example:: example_object.name_changed.emit('test') Signals can also be caught from multiple D-Bus objects using :py:meth:`catch_anywhere ` method. The async iterator will yield the path of the object that emitted the signal and the signal data. :py:meth:`catch_anywhere ` can be called from class but in such case the service name must be provided. Example:: async for path, x in ExampleInterface.name_changed.catch_anywhere('org.example.test'): print(f"On {path} caught: {x}") Subclass Overrides ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you define a subclass which overrides a declared D-Bus method or property you need to use :py:func:`dbus_method_async_override` and :py:func:`dbus_property_async_override` decorators. Overridden property can decorate a new setter. Overridden methods should take same number and type of arguments. Example: :: from sdbus import (dbus_method_async_override, dbus_property_async_override) # Some subclass class SubclassInterface(...): ... @dbus_method_async_override() async def upper(self, str_to_up: str) -> str: return 'Upper: ' + str_to_up.upper() @dbus_property_async_override() def str_prop(self) -> str: return 'Test property' + self.s # Setter needs to be decorated again to override @str_prop.setter def str_prop_setter(self, new_s: str) -> None: self.s = new_s.upper() Multiple interfaces ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A D-Bus object can have multiple interfaces with different methods and properties. To implement this define multiple interface classes and do a multiple inheritance on all interfaces the object has. Example: :: from sdbus import DbusInterfaceCommonAsync class ExampleInterface(DbusInterfaceCommonAsync, interface_name='org.example.myinterface' ): @dbus_method_async('i', 'i') async def double_int(self, an_int: int) -> None: return an_int * 2 class TestInterface(DbusInterfaceCommonAsync, interface_name='org.example.test' ): @dbus_method_async('as', 's') async def join_str(self, str_array: list[str]) -> str: return ''.join(str_array) class MultipleInterfaces(TestInterface, ExampleInterface): ... ``MultipleInterfaces`` class will have both ``test_method`` and ``example_method`` that will be wired to correct interface names. (``org.example.myinterface`` and ``org.example.test`` respectively) python-sdbus-0.14.0/docs/autodoc.rst000066400000000000000000000042651477456016000173740ustar00rootroot00000000000000Autodoc extensions ================== Python-sdbus has an extension for Sphinx autodoc that can document D-Bus interfaces. To use it include ``"sdbus.autodoc"`` extension in your ``conf.py`` file. .. code-block:: python extensions = ['sdbus.autodoc'] The extension can document interface class bodies. For example, `python-sdbus-networkmanager `_ uses it to document the classes. .. code-block:: rst .. autoclass:: sdbus_async.networkmanager.NetworkManagerDeviceBluetoothInterfaceAsync :members: .. warning:: Autodoc extension is early in development and has multiple issues. For example, the inheritance ``:inherited-members:`` does not work on the D-Bus elements. Writing docstrings ------------------- The D-Bus methods should be documented same way as the regular function would. See `Sphinx documentation on possible fields \ `_ Example docstring for a D-Bus method: .. code-block:: python @dbus_method_async('s', method_name='GetConnectionUnixProcessID') async def get_connection_pid(self, service_name: str) -> int: """Get process ID that owns a specified name. :param service_name: Service name to query. :return: PID of name owner :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError D-Bus properties and signals will be annotated with type taken from the stub function. .. code-block:: python @dbus_property_async('as') def features(self) -> list[str]: """List of D-Bus daemon features. Features include: * 'AppArmor' - Messages filtered by AppArmor on this bus. * 'HeaderFiltering' - Messages are filtered if they have incorrect \ header fields. * 'SELinux' - Messages filtered by SELinux on this bus. * 'SystemdActivation' - services activated by systemd if their \ .service file specifies a D-Bus name. """ raise NotImplementedError No parameters are supported at the moment for properties and signals. python-sdbus-0.14.0/docs/code_generator.rst000066400000000000000000000073321477456016000207140ustar00rootroot00000000000000Interface code generator ======================== Python-sdbus is able to generate the interfaces code from the D-Bus introspection XML. (either from a file or live object on D-Bus) Currently async interfaces code is generated by default. Blocking interfaces can be generated by passing ``--block`` option. Running code generator requires `Jinja `_ to be installed. .. warning:: Do NOT send the generator result to ``exec()`` function. Interface code MUST be inspected before running. The generated interfaces code will be syntactically correct but NOT stylistically. It is recommended running a code formatter on the generated code. (for example ``black``) Generating from XML files ------------------------- To run generator on files (such as found under ``/usr/share/dbus-1/interfaces/`` folder) execute the ``sdbus`` module with ``gen-from-file`` first argument and file paths to introspection XML files: .. code-block:: shell python -m sdbus gen-from-file /usr/share/dbus-1/interfaces/org.gnome.Shell.Screenshot.xml The generated interface code will be printed in to stdout. You can use shell redirection ``>`` to save it in to file. Multiple interface files can be passed which generates a file containing multiple interfaces. Generating from run-time introspection -------------------------------------- To run generator on some service on the D-Bus execute the ``sdbus`` module with ``gen-from-connection`` first argument, the service connection name as second and one or more object paths: .. code-block:: shell python -m sdbus gen-from-connection org.freedesktop.systemd1 /org/freedesktop/systemd1 The generated interface code will be printed in to stdout. You can use shell redirection ``>`` to save it in to file. Multiple object paths can be passed which generates a file containing all interfaces encountered in the objects. Pass ``--system`` option to use system bus instead of session bus. Renaming interfaces and members ------------------------------- *New in version 0.13.0.* Some interface and member names might conflict with Python keywords when converted from D-Bus introspection to Python code by gerator. The CLI interface allow to override the particular interface and member names using the ``--select-*`` and ``--set-name`` options. The selector options move the cursor to a particular interface and member Available override options: * ``--set-name`` Sets the name of currently selected element as it would be in generated Python code. Can be used if either interface or member is selected. * ``--select-interface`` Selects the interface using its D-Bus name. * ``--select-method`` Selects the method using its D-Bus name. An interface must be selected first. * ``--select-property`` Selects the property using its D-Bus name. An interface must be selected first. * ``--select-signal`` Selects the signal using its D-Bus name. An interface must be selected first. For example, an ``org.example.Interface`` interface has a property called ``Class``. When automatically converted the name will become ``class`` which is a reserved Python keyword. Using these CLI options it is possible to override the name of the property and class: .. code-block:: shell python -m sdbus gen-from-file \ org.example.interface.xml \ --select-interface org.example.Interface \ --set-name Example \ --select-property Class \ --set-name example_class This will generate following Python code: .. code-block:: python class Example: @dbus_property_async( property_signature="s", ) def example_class(self) -> str: raise NotImplementedError python-sdbus-0.14.0/docs/common_api.rst000066400000000000000000000110101477456016000200410ustar00rootroot00000000000000Common API ======================= These calls are shared between async and blocking API. Default bus +++++++++++ .. automodule:: sdbus.default_bus :members: .. py:currentmodule:: sdbus D-Bus connections calls +++++++++++++++++++++++ .. py:function:: sd_bus_open() Opens a new bus connection. The session bus will be opened when available or system bus otherwise. :return: Session or system bus. :rtype: SdBus .. py:function:: sd_bus_open_user() Opens a new user session bus connection. :return: Session bus. :rtype: SdBus .. py:function:: sd_bus_open_system() Opens a new system bus connection. :return: System bus. :rtype: SdBus .. py:function:: sd_bus_open_system_remote(host) Opens a new system bus connection on a remote host through SSH. Host can be prefixed with ``username@`` and followed by ``:port`` and ``/machine_name`` as in ``systemd-nspawn`` container name. :param str host: Host name to connect. :return: Remote system bus. :rtype: SdBus .. py:function:: sd_bus_open_system_machine(machine) Opens a new system bus connection in a systemd-nspawn container. Machine name can be prefixed with ``username@``. Special machine name ``.host`` indicates local system. :param str machine: Machine (container) name. :return: Remote system bus. :rtype: SdBus .. py:function:: sd_bus_open_user_machine(machine) Opens a new user session bus connection in a systemd-nspawn container. Opens root user bus session or can be prefixed with ``username@`` for a specific user. :param str machine: Machine (container) name. :return: Remote system bus. :rtype: SdBus Helper functions ++++++++++++++++++++++++++++++++++ .. py:function:: encode_object_path(prefix, external) Encode that arbitrary string as a valid object path prefixed with prefix. :param str prefix: Prefix path. Must be a valid object path. :param str external: Arbitrary string to identify object. :return: valid object path :rtype: str Example on how systemd encodes unit names on D-Bus: :: from sdbus import encode_object_path # System uses /org/freedesktop/systemd1/unit as prefix of all units # dbus.service is a name of D-Bus unit but dot . is not a valid object path s = encode_object_path('/org/freedesktop/systemd1/unit', 'dbus.service') print(s) # Prints: /org/freedesktop/systemd1/unit/dbus_2eservice .. py:function:: decode_object_path(prefix, full_path) Decode object name that was encoded with :py:func:`encode_object_path`. :param str prefix: Prefix path. Must be a valid object path. :param str full_path: Full path to be decoded. :return: Arbitrary name :rtype: str Example decoding systemd unit name: :: from sdbus import decode_object_path s = decode_object_path( '/org/freedesktop/systemd1/unit', '/org/freedesktop/systemd1/unit/dbus_2eservice' ) print(s) # Prints: dbus.service .. _dbus-flags: Flags +++++++++++++++++++++++++++++++++++ Flags are :py:obj:`int` values that should be ORed to combine. Example, :py:obj:`DbusDeprecatedFlag` plus :py:obj:`DbusHiddenFlag`: ``DbusDeprecatedFlag | DbusHiddenFlag`` .. py:data:: DbusDeprecatedFlag :type: int Mark this method or property as deprecated in introspection data. .. py:data:: DbusHiddenFlag :type: int Method or property will not show up in introspection data. .. py:data:: DbusUnprivilegedFlag :type: int Mark this method or property as unprivileged. This means anyone can call it. Only works for system bus as user session bus is fully trusted by default. .. py:data:: DbusNoReplyFlag :type: int This method does not have a reply message. It instantly returns and does not have any errors. .. py:data:: DbusPropertyConstFlag :type: int Mark that this property does not change during object life time. .. py:data:: DbusPropertyEmitsChangeFlag :type: int This property emits signal when it changes. .. py:data:: DbusPropertyEmitsInvalidationFlag :type: int This property emits signal when it invalidates. (means the value changed but does not include new value in the signal) .. py:data:: DbusPropertyExplicitFlag :type: int This property is too heavy to calculate so its not included in GetAll method call. .. py:data:: DbusSensitiveFlag :type: int Data in messages in sensitive and will be scrubbed from memory after message is red. python-sdbus-0.14.0/docs/conf.py000066400000000000000000000021441477456016000164750ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from os.path import abspath from sys import path project = 'python-sdbus' author = 'igo95862' source_suffix = '.rst' extensions = ['sdbus.autodoc'] html_theme = "sphinx_rtd_theme" autoclass_content = 'both' autodoc_member_order = 'bysource' path.insert(0, abspath('../src')) python-sdbus-0.14.0/docs/examples.rst000066400000000000000000000072561477456016000175570ustar00rootroot00000000000000Examples ======================= Asyncio client and server ++++++++++++++++++++++++++++++ In this example we create a simple example server and client. There are 3 files: * ``example_interface.py`` File that contains the interface definition. * ``example_server.py`` Server. * ``example_client.py`` Client. ``example_interface.py`` file: :: from sdbus import (DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, dbus_signal_async) # This is file only contains interface definition for easy import # in server and client files class ExampleInterface( DbusInterfaceCommonAsync, interface_name='org.example.interface' ): @dbus_method_async( input_signature='s', result_signature='s', ) async def upper(self, string: str) -> str: return string.upper() @dbus_property_async( property_signature='s', ) def hello_world(self) -> str: return 'Hello, World!' @dbus_signal_async( signal_signature='i' ) def clock(self) -> int: raise NotImplementedError ``example_server.py`` file: :: from asyncio import get_event_loop, sleep from random import randint from time import time from example_interface import ExampleInterface from sdbus import request_default_bus_name_async loop = get_event_loop() export_object = ExampleInterface() async def clock() -> None: """ This coroutine will sleep a random time and emit a signal with current clock """ while True: await sleep(randint(2, 7)) # Sleep a random time current_time = int(time()) # The interface we defined uses integers export_object.clock.emit(current_time) async def startup() -> None: """Perform async startup actions""" # Acquire a known name on the bus # Clients will use that name to address to this server await request_default_bus_name_async('org.example.test') # Export the object to D-Bus export_object.export_to_dbus('/') loop.run_until_complete(startup()) task_clock = loop.create_task(clock()) loop.run_forever() ``example_client.py`` file: :: from asyncio import get_event_loop from example_interface import ExampleInterface # Create a new proxy object example_object = ExampleInterface.new_proxy('org.example.test', '/') async def print_clock() -> None: # Use async for loop to print clock signals we receive async for x in example_object.clock: print('Got clock: ', x) async def call_upper() -> None: s = 'test string' s_after = await example_object.upper(s) print('Initial string: ', s) print('After call: ', s_after) async def get_hello_world() -> None: print('Remote property: ', await example_object.hello_world) loop = get_event_loop() # Always binds your tasks to a variable task_upper = loop.create_task(call_upper()) task_clock = loop.create_task(print_clock()) task_hello_world = loop.create_task(get_hello_world()) loop.run_forever() Start server before client. ``python example_server.py`` In separated terminal start client. ``python example_client.py`` Use CTRL-C to close client and server. You can also use :py:obj:`ExampleInterface` as a local object: :: from asyncio import run from example_interface import ExampleInterface example_object = ExampleInterface() async def test() -> None: print(await example_object.upper('test')) print(await example_object.hello_world) run(test()) python-sdbus-0.14.0/docs/exceptions.rst000066400000000000000000000217061477456016000201160ustar00rootroot00000000000000Exceptions ======================== .. py:currentmodule:: sdbus.exceptions Error name bound exceptions +++++++++++++++++++++++++++++++ These exceptions are bound to specific D-Bus error names. For example, :py:exc:`DbusFailedError` is bound to `org.freedesktop.DBus.Error.Failed` error name. This means if the remote object sends an error message with this error name the Python will receive this exception. When raised in a method callback an error message will be sent back to caller. See `list of error exceptions`_. New error bound exceptions +++++++++++++++++++++++++++++++ If you want to create a new error bound exception you should subclass it from :py:exc:`DbusFailedError` and provide a **unique** ``dbus_error_name`` attribute in the exception body definition. Example: :: class DbusExampleError(DbusFailedError): dbus_error_name = 'org.example.Error' If ``dbus_error_name`` is not unique the :py:exc:`ValueError` will be raised. Defining an exception will automatically bind incoming error message to this new exception. Existing exceptions can be manually binded using :py:func:`map_exception_to_dbus_error` function. Python built-in exceptions +++++++++++++++++++++++++++ All Python built-in exceptions are mapped to D-Bus errors. The D-Bus error name is created by appending ``org.python.Error.`` to the exception name. For example, ``AssertionError`` is bound to ``org.python.Error.AssertionError`` name. Functions +++++++++ .. py:function:: map_exception_to_dbus_error(exception, dbus_error_name) Map exception to a D-bus error. Error name must be unique. :param Type[Exception] exception: Exception to bind. :param str dbus_error_name: D-Bus error name to bind to. Other exceptions +++++++++++++++++++++++++ .. py:exception:: SdBusBaseError Base exceptions for all exceptions defined in sdbus. .. py:exception:: SdBusUnmappedMessageError Message error that is unmapped. The exceptions argument is a tuple of error name and error message. .. py:exception:: SdBusLibraryError sd-bus library returned error. Exception message contains line number and the error name. .. _name-request-exceptions: Name request exceptions +++++++++++++++++++++++ These exceptions will be raise if an error related to ownership of D-Bus names occurs when calling :py:func:`.request_default_bus_name_async` or :py:func:`.request_default_bus_name`. .. py:exception:: SdBusRequestNameError Common base exception for any name ownership error. .. py:exception:: SdBusRequestNameInQueueError Someone already owns the name but the request has been placed in queue. .. py:exception:: SdBusRequestNameExistsError Someone already owns the name. .. py:exception:: SdBusRequestNameAlreadyOwnerError The caller already owns the name. .. _list of error exceptions: Error name exception list ++++++++++++++++++++++++++++++ .. py:exception:: DbusFailedError Generic failure exception. Recommended to subclass to create a new exception. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.Failed .. py:exception:: DbusNoMemoryError Remote object is out of memory. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NoMemory .. py:exception:: DbusServiceUnknownError No service with such name exists. Probably should only be raised by bus daemon. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.ServiceUnknown .. py:exception:: DbusNameHasNoOwnerError No process owns the name you called. Probably should only be raised by bus daemon. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NameHasNoOwner .. py:exception:: DbusNoReplyError Timeout on reply. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NoReply .. py:exception:: DbusIOError Input/Output error. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.IOError .. py:exception:: DbusBadAddressError Bad address. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.BadAddress .. py:exception:: DbusNotSupportedError Something is unsupported on this platform. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NotSupported .. py:exception:: DbusLimitsExceededError Some resource was exhausted. (for example, file descriptors) .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.LimitsExceeded .. py:exception:: DbusAccessDeniedError Caller does not have enough privileges. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.AccessDenied .. py:exception:: DbusAuthFailedError Authentication failed. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.AuthFailed .. py:exception:: DbusNoServerError Unable to connect to bus. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NoServer .. py:exception:: DbusTimeoutError Socket timeout. This is different from :py:exc:`DbusNoReplyError` as here the connection to bus timeout not the remote object not replying. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.Timeout .. py:exception:: DbusNoNetworkError No network access. Encountered you use D-Bus over TCP or SSH. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.NoNetwork .. py:exception:: DbusAddressInUseError Address in use. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.AddressInUse .. py:exception:: DbusDisconnectedError Disconnected from bus. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.Disconnected .. py:exception:: DbusInvalidArgsError Method call args are invalid. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.InvalidArgs .. py:exception:: DbusFileNotFoundError File not found. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.FileNotFound .. py:exception:: DbusFileExistsError Generic failure exception. Recommended to subclass to create a new exception. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.FileExists .. py:exception:: DbusUnknownMethodError Unknown D-Bus method. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.UnknownMethod .. py:exception:: DbusUnknownObjectError Unknown D-Bus object. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.UnknownObject .. py:exception:: DbusUnknownInterfaceError Unknown D-Bus interface. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.UnknownInterface .. py:exception:: DbusUnknownPropertyError Unknown D-Bus property. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.UnknownProperty .. py:exception:: DbusPropertyReadOnlyError D-Bus property is read only. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.PropertyReadOnly .. py:exception:: DbusUnixProcessIdUnknownError PID does not exists. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.UnixProcessIdUnknown .. py:exception:: DbusInvalidSignatureError Invalid D-Bus type signature. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.InvalidSignature .. py:exception:: DbusInvalidFileContentError Invalid file content. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.InvalidFileContent .. py:exception:: DbusInconsistentMessageError D-Bus message is malformed. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.InconsistentMessage .. py:exception:: DbusMatchRuleNotFound Match rule does not exist. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.MatchRuleNotFound .. py:exception:: DbusMatchRuleInvalidError Match rule is invalid. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.MatchRuleInvalid .. py:exception:: DbusInteractiveAuthorizationRequiredError Requires interactive authorization. .. py:attribute:: dbus_error_name :type: str :value: org.freedesktop.DBus.Error.InteractiveAuthorizationRequired python-sdbus-0.14.0/docs/general.rst000066400000000000000000000240471477456016000173530ustar00rootroot00000000000000General Information =================== .. py:currentmodule:: sdbus .. _blocking-vs-async: Blocking vs Async +++++++++++++++++++++ Python-sdbus supports both blocking and async IO. Regular python functions are always blocking. Asyncio is a part of python standard library that allows non-blocking io. `Asyncio documentation `_ Generally blocking IO should only be used for simple scripts and programs that interact with existing D-Bus objects. Blocking: ^^^^^^^^^^^^^^^^^^^^^ * Blocking is easier to initiate (no event loop) * Properties behave exactly as Python properties do. (i.e. can assign with '=' operator) * Only allows one request at a time. * No D-Bus signals. * Cannot serve objects, only interact with existing object on D-Bus. :doc:`/sync_quick` :doc:`/sync_api` Asyncio: ^^^^^^^^^^^^^^^^^^^^^^^^ * Calls need to be ``await`` ed. * Multiple requests at the same time. * Serve object on D-Bus for other programs. * D-Bus Signals. :doc:`/asyncio_quick` :doc:`/asyncio_api` .. _dbus-types: D-Bus types conversion ++++++++++++++++++++++++ `D-Bus types reference `_ .. note:: Python integers are unlimited size but D-Bus integers are not. All integer types raise :py:exc:`OverflowError` if you try to pass number outside the type size. Unsigned integers range is ``0 < (2**bit_size)-1``. Signed integers range is ``-(2**(bit_size-1)) < (2**(bit_size-1))-1``. +-------------+------------+-----------------+--------------------------------------------------------------------+ | Name | D-Bus type | Python type | Description | +=============+============+=================+====================================================================+ | Boolean | b | :py:obj:`bool` | :py:obj:`True` or :py:obj:`False` | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Byte | y | :py:obj:`int` | Unsigned 8-bit integer. | | | | | **Note:** array of bytes (*ay*) has different type | | | | | in python domain. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Int16 | n | :py:obj:`int` | Signed 16-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Uint16 | q | :py:obj:`int` | Unsigned 16-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Int32 | i | :py:obj:`int` | Signed 32-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Uint32 | u | :py:obj:`int` | Unsigned 32-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Int64 | x | :py:obj:`int` | Signed 64-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Uint64 | t | :py:obj:`int` | Unsigned 64-bit integer. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Double | d | :py:obj:`float` | Float point number | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Unix FD | h | :py:obj:`int` | File descriptor | +-------------+------------+-----------------+--------------------------------------------------------------------+ | String | s | :py:obj:`str` | String | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Object | o | :py:obj:`str` | Syntactically correct D-Bus object path | | Path | | | | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Signature | g | :py:obj:`str` | D-Bus type signature | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Array | a | :py:obj:`list` | List of some single type. | | | | | | | | | | Example: ``as`` array of strings | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Byte Array | ay | :py:obj:`bytes` | Array of bytes. Not a unique type in D-Bus but a different type in | | | | | Python. Accepts both :py:obj:`bytes` and :py:obj:`bytearray`. | | | | | Used for binary data. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Struct | () | :py:obj:`tuple` | Tuple. | | | | | | | | | | Example: ``(isax)`` tuple of int, string and array of int. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Dictionary | a{} | :py:obj:`dict` | Dictionary with key type and value type. | | | | | | | | | | **Note:** Dictionary is always a part of array. | | | | | I.E. ``a{si}`` is the dict with string keys and integer values. | | | | | ``{si}`` is NOT a valid signature. | +-------------+------------+-----------------+--------------------------------------------------------------------+ | Variant | v | :py:obj:`tuple` | Unknown type that can be any single type. | | | | | In Python represented by a tuple of | | | | | a signature string and a single type. | | | | | | | | | | Example: ``("s", "test")`` variant of a single string | +-------------+------------+-----------------+--------------------------------------------------------------------+ Name conversions +++++++++++++++++++++ D-Bus uses CamelCase for method names. Python uses snake_case. When decorating a method name will be automatically translated from snake_case to CamelCase. Example: ``close_notification`` -> ``CloseNotification`` However, all decorators have a parameter to force D-Bus name to a specific value. See API documentation for a particular decorator. Default bus +++++++++++ Most object methods that take a bus as a parameter will use a thread-local default bus connection if a bus object is not explicitly passed. Session bus is default bus when running as a user and system bus otherwise. The :py:func:`request_default_bus_name_async ` and :py:func:`request_default_bus_name ` can be used to acquire a service name on the default bus. Use :py:func:`sd_bus_open_user` and :py:func:`sd_bus_open_system` to acquire a specific bus connection. The :py:func:`set_default_bus ` can be used to set the new thread-local bus. This should be done before any objects that take bus as an init argument are created. If no bus has been set the new bus will be initialized and set as thread-local default. The bus can also be set as default for the current context using :py:func:`set_context_default_bus `. The context refers to the standard library's ``contextvars`` module context variables frequently used in asyncio frameworks. Context-local default bus has higher priority over thread-local default bus. Glossary +++++++++++++++++++++ * **Bus** object representing connection to D-Bus. * **Proxy** Python object that represents an object on D-Bus. Without proxy you manipulate messages directly. * **Remote** something that exists outside current Python process. * **Local** something that exists inside current Python scope. * **Service Name** a well known name that an process can acquire on D-Bus. For example, systemd acquires ``org.freedesktop.systemd1`` name. * **Signature** D-Bus type definition. Represented by a string. See :ref:`dbus-types`. Contents ++++++++++++++++++++ * :ref:`genindex` * :doc:`/api_index` * :ref:`search` python-sdbus-0.14.0/docs/index.rst000066400000000000000000000030371477456016000170410ustar00rootroot00000000000000Welcome to Python-sdbus documentation! ======================================================= Python-sdbus is the python D-Bus library that aim to use the modern features of python * `Asyncio `_ * `Type hints `_ * `Based on fast sd-bus `_ * Unified client/server interface classes. Write interface class once. * D-Bus methods can have keyword and default arguments. D-Bus ----------- D-Bus is the inter-process communication standard commonly used on Linux desktop. This documentation expects you to be familiar with D-Bus concepts and conventions. If you are unfamiliar with D-Bus you might want to read following pages: `Wikipedia page `_ `Lennart Poettering post about D-Bus `_ `D-Bus specification by freedesktop.org `_ `Install D-Spy D-Bus debugger and observe services and objects on your D-Bus `_ .. toctree:: :maxdepth: 3 :caption: Contents: general common_api sync_quick sync_api asyncio_quick asyncio_api asyncio_deep exceptions utils examples proxies code_generator autodoc unittest api_index Indices and tables ================== * :ref:`genindex` * :doc:`/api_index` * :ref:`search` python-sdbus-0.14.0/docs/proxies.rst000066400000000000000000000013631477456016000174230ustar00rootroot00000000000000Interfaces repository ========================================== python-sdbus includes two namespace packages ``sdbus_async`` and ``sdbus_block`` which are used for proxies. For example, D-Bus daemon interface (which comes by default) can be found under ``sdbus_async.dbus_daemon`` for async binds and ``sdbus_block.dbus_daemon`` for blocking binds. Known proxies ------------- .. toctree:: :hidden: proxies/dbus This list contains the known python-sdbus interface collections: * :doc:`proxies/dbus`. Built-in. * `Notifications `_. * `NetworkManager `_. * `Secrets `_. python-sdbus-0.14.0/docs/proxies/000077500000000000000000000000001477456016000166665ustar00rootroot00000000000000python-sdbus-0.14.0/docs/proxies/dbus.rst000066400000000000000000000001731477456016000203560ustar00rootroot00000000000000D-Bus daemon interface ============================= .. autoclass:: sdbus_async.dbus_daemon.FreedesktopDbus :members: python-sdbus-0.14.0/docs/requirements.txt000066400000000000000000000001341477456016000204570ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 sphinx_rtd_theme python-sdbus-0.14.0/docs/sync_api.rst000066400000000000000000000177111477456016000175430ustar00rootroot00000000000000Blocking API ============ Classes +++++++++++++++ .. py:currentmodule:: sdbus .. py:class:: DbusInterfaceCommon(interface_name) D-Bus interface class. D-Bus methods and properties should be defined using :py:func:`dbus_property` and :py:func:`dbus_method` decorators. :param str interface_name: Sets the D-Bus interface name that will be used for all properties and methods defined in the body of the class .. py:method:: __init__(service_name, object_path, [bus]) Init will create a proxy to a remote object :param str service_name: Remote object D-Bus connection name. For example, systemd uses ``org.freedesktop.systemd1`` :param str object_path: Remote object D-Bus path. Should be a forward slash separated path. Starting object is usually ``/``. Example: ``/org/freedesktop/systemd/unit/dbus_2eservice`` :param SdBus bus: Optional D-Bus connection object. If not passed the default D-Bus will be used. .. py:method:: dbus_ping() Pings the remote service using D-Bus. Useful to test if connection or remote service is alive. .. warning:: This method is ignores the particular object path meaning it can NOT be used to test if object exist. .. py:method:: dbus_machine_id() Returns the machine UUID of D-Bus the object is connected to. :return: machine UUID :rtype: str .. py:method:: dbus_introspect() Get D-Bus introspection XML. It is users responsibility to parse that data. :return: string with introspection XML :rtype: str .. py:method:: properties_get_all_dict() Get all object properties as a dictionary where keys are member names and values are properties values. Equivalent to ``GetAll`` method of the ``org.freedesktop.DBus.Properties`` interface but the member names are automatically translated to python names. (internally calls it for each interface used in class definition) :param str on_unknown_member: If an unknown D-Bus property was encountered either raise an ``"error"`` (default), ``"ignore"`` the property or ``"reuse"`` the D-Bus name for the member. :return: dictionary of properties :rtype: dict[str, Any] Example: :: from sdbus import (DbusInterfaceCommon, dbus_method, dbus_property) class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.my' ): # Method that takes an integer and does not return anything @dbus_method('u') def close_notification(self, an_int: int) -> None: raise NotImplementedError # Method that does not take any arguments and returns a list of str @dbus_method() def get_capabilities(self) -> list[str]: raise NotImplementedError # Method that takes a dict of {str: str} and returns an int @dbus_method('a{ss}') def count_entries(self, a_dict: dict[str, str]) -> int: raise NotImplementedError # Read only property of int @dbus_property() def test_int(self) -> int: raise NotImplementedError # Read/Write property of str @dbus_property('s') def test_string(self) -> str: raise NotImplementedError .. py:class:: DbusObjectManagerInterface(interface_name) This class is almost identical to :py:class:`DbusInterfaceCommon` but implements `ObjectManager `_ interface. .. py:method:: get_managed_objects() Get the objects this object manager in managing. :return: Triple nested dictionary that contains all the objects paths with their properties values. dict[ObjectPath, dict[InterfaceName, dict[PropertyName, PropertyValue]]] :rtype: dict[str, dict[str, dict[str, Any]]] Decorators +++++++++++++++ .. py:decorator:: dbus_method([input_signature, [flags, [method_name]]]) Define D-Bus method Decorated function becomes linked to D-Bus method. Always use round brackets () even when not passing any arguments. :param str input_signature: D-Bus input signature. Defaults to "" meaning method takes no arguments. Required if method takes any arguments. :param int flags: modifies behavior. No effect on remote connections. Defaults to 0 meaning no special behavior. See :ref:`dbus-flags` . :param str method_name: Explicitly define remote method name. Usually not required as remote method name will be constructed based on original method name. Defining methods example: :: from sdbus import DbusInterfaceCommon, dbus_method class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.my' ): # Method that takes an integer and does not return anything @dbus_method('u') def close_notification(self, an_int: int) -> None: raise NotImplementedError # Method that does not take any arguments and returns a list of str @dbus_method() def get_capabilities(self) -> list[str]: raise NotImplementedError # Method that takes a dict of {str: str} and returns an int @dbus_method('a{ss}') def count_entries(self, a_dict: dict[str, str]) -> int: raise NotImplementedError Calling methods example:: # Initialize the object d = ExampleInterface( service_name='org.example.test', object_path='/', ) d.close_notification(1234) l = d.get_capabilities() d.count_entries({'a': 'asdasdasd', 'b': 'hgterghead213d'}) .. py:decorator:: dbus_property([property_signature, [flags, [property_name]]]) Define D-Bus property Property works just like @property decorator would. Always use round brackets () even when not passing any arguments. Read only property can be indicated by passing empty D-Bus signature "". Trying to assign a read only property will raise :py:exc:`AttributeError` :param str property_signature: D-Bus property signature. Empty signature "" indicates read-only property. Defaults to empty signature "". Required only for writable properties. :param int flags: modifies behavior. No effect on remote connections. Defaults to 0 meaning no special behavior. See :ref:`dbus-flags` . :param str property_name: Explicitly define remote property name. Usually not required as remote property name will be constructed based on original method name. Defining properties example: :: from sdbus import DbusInterfaceCommon, dbus_property class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.myproperty' ): # Property of int @dbus_property('i') def test_int(self) -> int: raise NotImplementedError # Property of str @dbus_property('s') def test_string(self) -> str: raise NotImplementedError Properties usage example:: # Initialize the object d = ExampleInterface( service_name='org.example.test', object_path='/', ) # Print the int print(d.test_int) # Assign new string d.test_string = 'some_string' # Print it print(d.test_string) * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-sdbus-0.14.0/docs/sync_quick.rst000066400000000000000000000114001477456016000200730ustar00rootroot00000000000000Blocking quick start +++++++++++++++++++++ .. py:currentmodule:: sdbus Interface classes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python-sdbus works by declaring interface classes. Interface classes for blocking IO should be derived from :py:class:`DbusInterfaceCommon`. The class constructor takes ``interface_name`` keyword to determine the D-Bus interface name for all D-Bus elements declared in the class body. Example:: class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.myinterface' ): ... Interface class body should contain the definitions of methods and properties using the decorators :py:func:`dbus_method` and :py:func:`dbus_property` respectively. Example:: from sdbus import (DbusInterfaceCommon, dbus_method, dbus_property) class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.myinterface' ): # Method that takes an integer and does not return anything @dbus_method('u') def close_notification(self, an_int: int) -> None: raise NotImplementedError # Read only property of int @dbus_property() def test_int(self) -> int: raise NotImplementedError This is an interface of that defines a one D-Bus method and one property. The actual body of the decorated function will not be called. Instead the call will be routed through D-Bus to a another process. Interface can have non-decorated functions that will act as regular methods. Blocking IO can only interact with existing D-Bus objects and can not be served for other processes to interact with. See :ref:`blocking-vs-async` Initiating proxy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :py:meth:`DbusInterfaceCommon.__init__` method takes service_name and object_path of the remote object that the object will proxy to. Example creating a proxy and calling method:: ... # Initialize the object d = ExampleInterface( service_name='org.example.test', object_path='/', ) d.close_notification(1234) .. note:: Successfully initiating a proxy object does NOT guarantee that the D-Bus object exists. Methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Methods are functions wrapped with :py:func:`dbus_method` decorator. If the remote object sends an error reply an exception with base of :py:exc:`.DbusFailedError` will be raised. See :doc:`/exceptions` for list of exceptions. The wrapped function will not be called. Its recommended to set the function to ``raise NotImplementedError``. Example: :: from sdbus import DbusInterfaceCommon, dbus_method class ExampleInterface(...): ... # Body of some class @dbus_method('u') def close_notification(self, an_int: int) -> None: raise NotImplementedError Properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D-Bus property is defined by wrapping a function with :py:func:`dbus_property` decorator. Example: :: from sdbus import DbusInterfaceCommon, dbus_property class ExampleInterface(...): ... # Body of some class # Property of str @dbus_property('s') def test_string(self) -> str: raise NotImplementedError The new property behaves very similar to Pythons :py:func:`property` decorator. :: # Initialize the proxy d = ExampleInterface( service_name='org.example.test', object_path='/', ) # Print it print(d.test_string) # Assign new string d.test_string = 'some_string' If property is read-only when :py:exc:`.DbusPropertyReadOnlyError` will be raised. Multiple interfaces ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A D-Bus object can have multiple interfaces with different methods and properties. To implement this define multiple interface classes and do a multiple inheritance on all interfaces the object has. Example: :: from sdbus import DbusInterfaceCommon, dbus_method class ExampleInterface(DbusInterfaceCommon, interface_name='org.example.myinterface' ): @dbus_method('i') def example_method(self, an_int: int) -> None: raise NotImplementedError class TestInterface(DbusInterfaceCommon, interface_name='org.example.test' ): @dbus_method('as') def test_method(self, str_array: list[str]) -> None: raise NotImplementedError class MultipleInterfaces(TestInterface, ExampleInterface): ... ``MultipleInterfaces`` class will have both ``test_method`` and ``example_method`` that will be proxied to correct interface names. (``org.example.myinterface`` and ``org.example.test`` respectively) python-sdbus-0.14.0/docs/unittest.rst000066400000000000000000000053551477456016000176160ustar00rootroot00000000000000Unit testing ============ Python-sdbus provides several utilities to enable unit testing. .. py:currentmodule:: sdbus.unittest .. py:class:: IsolatedDbusTestCase Extension of `unittest.IsolatedAsyncioTestCase `__ from standard library. Creates an isolated instance of session D-Bus. The D-Bus will be closed and cleaned up after tests are finished. Requires ``dbus-daemon`` executable be installed. Example:: from sdbus import DbusInterfaceCommonAsync, dbus_method_async from sdbus.unittest import IsolatedDbusTestCase class TestInterface(DbusInterfaceCommonAsync, interface_name='org.test.test', ): @dbus_method_async("s", "s") async def upper(self, string: str) -> str: """Uppercase the input""" return string.upper() def initialize_object() -> tuple[TestInterface, TestInterface]: test_object = TestInterface() test_object.export_to_dbus('/') test_object_connection = TestInterface.new_proxy( "org.example.test", '/') return test_object, test_object_connection class TestProxy(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() await self.bus.request_name_async("org.example.test", 0) async def test_method_kwargs(self) -> None: test_object, test_object_connection = initialize_object() self.assertEqual( 'TEST', await test_object_connection.upper('test'), ) .. py:attribute:: bus :type: SdBus Bus instance connected to isolated D-Bus environment. It is also set as a default bus. .. py:method:: assertDbusSignalEmits(signal, timeout=1) Assert that a given signal was emitted at least once within the given timeout. :param signal: D-Bus signal object. Can be a signal from either local or proxy object. :param Union[int, float] timeout: Maximum wait time until first captured signal. Should be used as an async context manager. The context manager exits as soon as first signal is captured. The object returned by context manager has following attributes: .. py:attribute:: output :type: list[Any] List of captured data. Example:: async with self.assertDbusSignalEmits(test_object.test_signal) as signal_record: test_object.test_signal.emit("test") self.assertEqual(["test"], signal_record.output) *New in version 0.12.0.* python-sdbus-0.14.0/docs/utils.rst000066400000000000000000000007311477456016000170700ustar00rootroot00000000000000Utilities ========= Parsing utilities +++++++++++++++++ Parse unweildy D-Bus structures in to Python native objects and names. Available under ``sdbus.utils.parse`` subpackage. .. automodule:: sdbus.utils.parse :members: Inspect utilities +++++++++++++++++ Inspect D-Bus objects and retrieve their D-Bus related attributes such as D-Bus object paths and etc... Available under ``sdbus.utils.inspect`` subpackage. .. automodule:: sdbus.utils.inspect :members: python-sdbus-0.14.0/examples/000077500000000000000000000000001477456016000160635ustar00rootroot00000000000000python-sdbus-0.14.0/examples/simple/000077500000000000000000000000001477456016000173545ustar00rootroot00000000000000python-sdbus-0.14.0/examples/simple/client.py000066400000000000000000000033041477456016000212040ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import new_event_loop from example_interface import ExampleInterface # Create a new proxied object example_object = ExampleInterface.new_proxy('org.example.test', '/') async def print_clock() -> None: # Use async for loop to print clock signals we receive async for x in example_object.clock: print('Got clock: ', x) async def call_upper() -> None: s = 'test string' s_after = await example_object.upper(s) print('Initial string: ', s) print('After call: ', s_after) async def get_hello_world() -> None: print('Remote property: ', await example_object.hello_world) loop = new_event_loop() # Always bind your tasks to a variable task_upper = loop.create_task(call_upper()) task_clock = loop.create_task(print_clock()) task_hello_world = loop.create_task(get_hello_world()) loop.run_forever() python-sdbus-0.14.0/examples/simple/example_interface.py000066400000000000000000000031231477456016000234000ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from sdbus import ( DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, dbus_signal_async, ) # This is file only contains interface definition for easy import # in server and client files class ExampleInterface( DbusInterfaceCommonAsync, interface_name='org.example.interface' ): @dbus_method_async( input_signature='s', result_signature='s', ) async def upper(self, string: str) -> str: return string.upper() @dbus_property_async( property_signature='s', ) def hello_world(self) -> str: return 'Hello, World!' @dbus_signal_async( signal_signature='i' ) def clock(self) -> int: raise NotImplementedError python-sdbus-0.14.0/examples/simple/server.py000066400000000000000000000034721477456016000212420ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import new_event_loop, sleep from random import randint from time import time from example_interface import ExampleInterface from sdbus import request_default_bus_name_async loop = new_event_loop() export_object = ExampleInterface() async def clock() -> None: """ This coroutine will sleep a random time and emit a signal with current clock """ while True: await sleep(randint(2, 7)) # Sleep a random time current_time = int(time()) # The interface we defined uses integers export_object.clock.emit(current_time) async def startup() -> None: """Perform async startup actions""" # Acquire a known name on the bus # Clients will use that name to address this server await request_default_bus_name_async('org.example.test') # Export the object to D-Bus export_object.export_to_dbus('/') loop.run_until_complete(startup()) task_clock = loop.create_task(clock()) loop.run_forever() python-sdbus-0.14.0/meson.build000066400000000000000000000015271477456016000164140ustar00rootroot00000000000000project('python-sdbus', 'c') python_module = import('python') python = python_module.find_installation('python3') mypy = find_program( 'mypy', required : false, ) flake8 = find_program( 'flake8', required : false, ) linter_script = files('./tools/run_py_linters.py') if mypy.found() and flake8.found() run_target( 'lint-python', command : [python, linter_script, 'lint'], ) else warning('Mypy or Flake8 not found. Python linting disabled') endif autopep8 = find_program( 'autopep8', required : false, ) isort = find_program( 'isort', required : false, ) if autopep8.found() and isort.found() run_target( 'format-python', command : [python, linter_script, 'format'], ) else warning('autopep8 or isort not found. Python formating disabled') endif subdir('src') python-sdbus-0.14.0/setup.py000066400000000000000000000122151477456016000157600ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from os import environ from subprocess import DEVNULL, PIPE from subprocess import run as subprocess_run from typing import Optional from setuptools import Extension, setup c_macros: list[tuple[str, Optional[str]]] = [] def get_libsystemd_version() -> int: process = subprocess_run( args=('pkg-config', '--modversion', 'libsystemd'), stderr=DEVNULL, stdout=PIPE, check=True, text=True, ) result_str = process.stdout # Version can either be like 250 or 250.10 first_component = result_str.split(".")[0] return int(first_component) if not environ.get('PYTHON_SDBUS_USE_IGNORE_SYSTEMD_VERSION'): systemd_version = get_libsystemd_version() if systemd_version < 246: c_macros.append(('LIBSYSTEMD_NO_VALIDATION_FUNCS', None)) if systemd_version < 248: c_macros.append(('LIBSYSTEMD_NO_OPEN_USER_MACHINE', None)) def get_link_arguments() -> list[str]: process = subprocess_run( args=('pkg-config', '--libs-only-l', 'libsystemd'), stderr=DEVNULL, stdout=PIPE, check=True, ) result_str = process.stdout.decode('utf-8') return result_str.rstrip(' \n').split(' ') link_arguments: list[str] = get_link_arguments() if environ.get('PYTHON_SDBUS_USE_STATIC_LINK'): # Link statically against libsystemd and libcap link_arguments = ['-Wl,-Bstatic', *link_arguments, '-lcap', '-Wl,-Bdynamic', '-lrt', '-lpthread'] link_arguments.append('-flto') compile_arguments: list[str] = ['-flto'] use_limited_api = False if environ.get('PYTHON_SDBUS_USE_LIMITED_API'): c_macros.append(('Py_LIMITED_API', '0x03090000')) use_limited_api = True if __name__ == '__main__': with open('./README.md') as f: long_description = f.read() setup( name='sdbus', description=('Modern Python D-Bus library. ' 'Based on sd-bus from libsystemd.'), long_description=long_description, long_description_content_type='text/markdown', version='0.14.0', url='https://github.com/igo95862/python-sdbus', author='igo95862', author_email='igo95862@yandex.ru', license='LGPL-2.1-or-later', keywords='dbus ipc linux freedesktop', project_urls={ 'Documentation': 'https://python-sdbus.readthedocs.io/en/latest/', 'Source': 'https://github.com/igo95862/python-sdbus/', 'Tracker': 'https://github.com/igo95862/python-sdbus/issues/', }, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', ( 'License :: OSI Approved :: ' 'GNU Lesser General Public License v2 or later (LGPLv2+)' ), 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=[ 'sdbus', 'sdbus.utils', 'sdbus_async.dbus_daemon', 'sdbus_block.dbus_daemon', ], package_dir={ 'sdbus': 'src/sdbus', 'sdbus_async.dbus_daemon': 'src/sdbus_async/dbus_daemon', 'sdbus_block.dbus_daemon': 'src/sdbus_block/dbus_daemon', }, package_data={ 'sdbus': [ 'py.typed', 'sd_bus_internals.pyi', 'sd_bus_internals.h', ], 'sdbus_async.dbus_daemon': [ 'py.typed', ], 'sdbus_block.dbus_daemon': [ 'py.typed', ], }, python_requires='>=3.9', ext_modules=[ Extension( 'sdbus.sd_bus_internals', [ 'src/sdbus/sd_bus_internals.c', 'src/sdbus/sd_bus_internals_bus.c', 'src/sdbus/sd_bus_internals_funcs.c', 'src/sdbus/sd_bus_internals_interface.c', 'src/sdbus/sd_bus_internals_message.c', ], extra_compile_args=compile_arguments, extra_link_args=link_arguments, define_macros=c_macros, py_limited_api=use_limited_api, ) ], ) python-sdbus-0.14.0/src/000077500000000000000000000000001477456016000150345ustar00rootroot00000000000000python-sdbus-0.14.0/src/meson.build000066400000000000000000000000201477456016000171660ustar00rootroot00000000000000subdir('sdbus') python-sdbus-0.14.0/src/sdbus/000077500000000000000000000000001477456016000161545ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus/__init__.py000066400000000000000000000120561477456016000202710ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from .dbus_exceptions import ( DbusAccessDeniedError, DbusAddressInUseError, DbusAuthFailedError, DbusBadAddressError, DbusDisconnectedError, DbusFailedError, DbusFileExistsError, DbusFileNotFoundError, DbusInconsistentMessageError, DbusInteractiveAuthorizationRequiredError, DbusInvalidArgsError, DbusInvalidFileContentError, DbusInvalidSignatureError, DbusIOError, DbusLimitsExceededError, DbusMatchRuleInvalidError, DbusMatchRuleNotFound, DbusNameHasNoOwnerError, DbusNoMemoryError, DbusNoNetworkError, DbusNoReplyError, DbusNoServerError, DbusNotSupportedError, DbusPropertyReadOnlyError, DbusServiceUnknownError, DbusTimeoutError, DbusUnixProcessIdUnknownError, DbusUnknownInterfaceError, DbusUnknownMethodError, DbusUnknownObjectError, DbusUnknownPropertyError, ) from .dbus_proxy_async_interfaces import DbusInterfaceCommonAsync from .dbus_proxy_async_method import ( dbus_method_async, dbus_method_async_override, get_current_message, ) from .dbus_proxy_async_object_manager import DbusObjectManagerInterfaceAsync from .dbus_proxy_async_property import ( dbus_property_async, dbus_property_async_override, ) from .dbus_proxy_async_signal import dbus_signal_async from .dbus_proxy_sync_interfaces import ( DbusInterfaceCommon, DbusObjectManagerInterface, ) from .dbus_proxy_sync_method import dbus_method from .dbus_proxy_sync_property import dbus_property from .default_bus import ( get_default_bus, request_default_bus_name, request_default_bus_name_async, set_context_default_bus, set_default_bus, ) from .sd_bus_internals import ( DbusDeprecatedFlag, DbusHiddenFlag, DbusNoReplyFlag, DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, DbusPropertyEmitsInvalidationFlag, DbusPropertyExplicitFlag, DbusSensitiveFlag, DbusUnprivilegedFlag, SdBus, SdBusBaseError, SdBusLibraryError, SdBusUnmappedMessageError, decode_object_path, encode_object_path, map_exception_to_dbus_error, sd_bus_open, sd_bus_open_system, sd_bus_open_system_machine, sd_bus_open_system_remote, sd_bus_open_user, sd_bus_open_user_machine, ) __all__ = ( 'DbusAccessDeniedError', 'DbusAddressInUseError', 'DbusAuthFailedError', 'DbusBadAddressError', 'DbusDisconnectedError', 'DbusFailedError', 'DbusFileExistsError', 'DbusFileNotFoundError', 'DbusInconsistentMessageError', 'DbusInteractiveAuthorizationRequiredError', 'DbusInvalidArgsError', 'DbusInvalidFileContentError', 'DbusInvalidSignatureError', 'DbusIOError', 'DbusLimitsExceededError', 'DbusMatchRuleInvalidError', 'DbusMatchRuleNotFound', 'DbusNameHasNoOwnerError', 'DbusNoMemoryError', 'DbusNoNetworkError', 'DbusNoReplyError', 'DbusNoServerError', 'DbusNotSupportedError', 'DbusPropertyReadOnlyError', 'DbusServiceUnknownError', 'DbusTimeoutError', 'DbusUnixProcessIdUnknownError', 'DbusUnknownInterfaceError', 'DbusUnknownMethodError', 'DbusUnknownObjectError', 'DbusUnknownPropertyError', 'DbusInterfaceCommonAsync', 'DbusObjectManagerInterfaceAsync', 'dbus_method_async', 'dbus_method_async_override', 'dbus_property_async', 'dbus_property_async_override', 'get_current_message', 'dbus_signal_async', 'DbusInterfaceCommon', 'DbusObjectManagerInterface', 'dbus_method', 'dbus_property', "get_default_bus", "request_default_bus_name", "request_default_bus_name_async", "set_context_default_bus", "set_default_bus", 'DbusDeprecatedFlag', 'DbusHiddenFlag', 'DbusNoReplyFlag', 'DbusPropertyConstFlag', 'DbusPropertyEmitsChangeFlag', 'DbusPropertyEmitsInvalidationFlag', 'DbusPropertyExplicitFlag', 'DbusSensitiveFlag', 'DbusUnprivilegedFlag', 'SdBus', 'SdBusBaseError', 'SdBusLibraryError', 'SdBusUnmappedMessageError', 'decode_object_path', 'encode_object_path', 'map_exception_to_dbus_error', 'sd_bus_open', 'sd_bus_open_system', 'sd_bus_open_system_machine', 'sd_bus_open_system_remote', 'sd_bus_open_user', 'sd_bus_open_user_machine', ) python-sdbus-0.14.0/src/sdbus/__main__.py000066400000000000000000000275511477456016000202600ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from argparse import SUPPRESS, Action, ArgumentParser from dataclasses import dataclass, field from pathlib import Path from sys import stdout from typing import TYPE_CHECKING from .interface_generator import ( generate_py_file, interfaces_from_file, interfaces_from_str, ) if TYPE_CHECKING: from typing import Optional from .interface_generator import DbusInterfaceIntrospection @dataclass class RenameMember: new_name: Optional[str] = None current_arg: Optional[str] = None arg_renames: dict[str, str] = field(default_factory=dict) @dataclass class RenameInterface: new_name: Optional[str] = None current_member: Optional[RenameMember] = None methods: dict[str, RenameMember] = field(default_factory=dict) properties: dict[str, RenameMember] = field(default_factory=dict) signals: dict[str, RenameMember] = field(default_factory=dict) @dataclass class RenameRoot: current_interface: Optional[RenameInterface] = None interfaces: dict[str, RenameInterface] = field(default_factory=dict) rename_root = RenameRoot() # def rename_args(member_rename): # ... def rename_members( interface: DbusInterfaceIntrospection, interface_rename: RenameInterface, ) -> None: for m_member in interface.methods: m_rename = interface_rename.methods.get(m_member.method_name) if m_rename is None: continue if m_rename.new_name is not None: m_member.python_name = m_rename.new_name for p_member in interface.properties: p_rename = interface_rename.properties.get(p_member.method_name) if p_rename is None: continue if p_rename.new_name is not None: p_member.python_name = p_rename.new_name for s_member in interface.signals: s_rename = interface_rename.signals.get(s_member.method_name) if s_rename is None: continue if s_rename.new_name is not None: s_member.python_name = s_rename.new_name def rename_interfaces( interfaces: list[DbusInterfaceIntrospection] ) -> None: for interface in interfaces: dbus_interface_name = interface.interface_name this_interface_rename = rename_root.interfaces.get(dbus_interface_name) if this_interface_rename is None: continue if this_interface_rename.new_name is not None: interface.python_name = this_interface_rename.new_name rename_members(interface, this_interface_rename) def run_gen_from_connection( connection_name: str, object_paths: list[str], system: bool, imports_header: bool, do_async: bool, ) -> None: connection_name = connection_name object_paths = object_paths from .dbus_proxy_sync_interfaces import DbusInterfaceCommon if system: from .default_bus import set_default_bus from .sd_bus_internals import sd_bus_open_system set_default_bus(sd_bus_open_system()) interfaces: list[DbusInterfaceIntrospection] = [] for object_path in object_paths: connection = DbusInterfaceCommon(connection_name, object_path) itrospection = connection.dbus_introspect() interfaces.extend(interfaces_from_str(itrospection)) rename_interfaces(interfaces) stdout.write( generate_py_file( interfaces, imports_header, do_async, ) ) def run_gen_from_file( filenames: list[str], imports_header: bool, do_async: bool, ) -> None: interfaces: list[DbusInterfaceIntrospection] = [] for file in filenames: interfaces.extend(interfaces_from_file(file)) rename_interfaces(interfaces) stdout.write( generate_py_file( interfaces, imports_header, do_async, ) ) class ActionSelectInterface(Action): def __call__( self, parser: ArgumentParser, namespace: object, values: object, option_string: Optional[str] = None, ) -> None: if not isinstance(values, str): raise TypeError( f"Expected --select-interface to be string, got {values!r}" ) interface_rename = rename_root.interfaces.get(values) if interface_rename is None: interface_rename = RenameInterface() rename_root.interfaces[values] = interface_rename rename_root.current_interface = interface_rename class ActionSelectMethod(Action): def __call__( self, parser: ArgumentParser, namespace: object, values: object, option_string: Optional[str] = None, ) -> None: if not isinstance(values, str): raise TypeError( f"Expected --select-method to be string, got {values!r}" ) current_interface = rename_root.current_interface if current_interface is None: raise ValueError( "No D-Bus interface selected. " "Use --select-interface option." ) method_rename = current_interface.methods.get(values) if method_rename is None: method_rename = RenameMember() current_interface.methods[values] = method_rename current_interface.current_member = method_rename class ActionSelectProperty(Action): def __call__( self, parser: ArgumentParser, namespace: object, values: object, option_string: Optional[str] = None, ) -> None: if not isinstance(values, str): raise TypeError( f"Expected --select-property to be string, got {values!r}" ) current_interface = rename_root.current_interface if current_interface is None: raise ValueError( "No D-Bus interface selected. " "Use --select-interface option." ) property_rename = current_interface.properties.get(values) if property_rename is None: property_rename = RenameMember() current_interface.properties[values] = property_rename current_interface.current_member = property_rename class ActionSelectSignal(Action): def __call__( self, parser: ArgumentParser, namespace: object, values: object, option_string: Optional[str] = None, ) -> None: if not isinstance(values, str): raise TypeError( f"Expected --select-signal to be string, got {values!r}" ) current_interface = rename_root.current_interface if current_interface is None: raise ValueError( "No D-Bus interface selected. " "Use --select-interface option." ) signal_rename = current_interface.signals.get(values) if signal_rename is None: signal_rename = RenameMember() current_interface.signals[values] = signal_rename current_interface.current_member = signal_rename class ActionSetName(Action): def __call__( self, parser: ArgumentParser, namespace: object, values: object, option_string: Optional[str] = None, ) -> None: if not isinstance(values, str): raise TypeError( f"Expected --set-name to be string, got {values!r}" ) current_interface = rename_root.current_interface current_member = ( current_interface.current_member if current_interface is not None else None ) if current_member is not None: current_member.new_name = values return if current_interface is not None: current_interface.new_name = values return raise ValueError( "No D-Bus element to rename. " "Use --select-* options to select element." ) def generator_main(args: Optional[list[str]] = None) -> None: main_arg_parser = ArgumentParser( prog="sdbus", ) subparsers = main_arg_parser.add_subparsers( required=True, title="subcommands", ) generate_from_file_parser = subparsers.add_parser('gen-from-file') generate_from_file_parser.set_defaults(func=run_gen_from_file) generate_from_connection = subparsers.add_parser('gen-from-connection') generate_from_connection.set_defaults(func=run_gen_from_connection) # Common options for subparser in (generate_from_file_parser, generate_from_connection): subparser.add_argument( "--no-imports-header", action="store_false", dest="imports_header", help="Do NOT include 'import' header", ) subparser.add_argument( "--imports-header", action="store_true", default=True, dest="imports_header", help="Include 'import' header (default)", ) subparser.add_argument( "--async", action="store_true", default=True, dest="do_async", help="Generate async interfaces (default)", ) subparser.add_argument( "--block", action="store_false", dest="do_async", help="Generate blocking interfaces", ) subparser.add_argument( "--select-interface", action=ActionSelectInterface, default=SUPPRESS, help="Select D-Bus interface to adjust" ) subparser.add_argument( "--select-method", action=ActionSelectMethod, default=SUPPRESS, help="Select D-Bus method to adjust" ) subparser.add_argument( "--select-property", action=ActionSelectProperty, default=SUPPRESS, help="Select D-Bus property to adjust" ) subparser.add_argument( "--select-signal", action=ActionSelectSignal, default=SUPPRESS, help="Select D-Bus signal to adjust" ) subparser.add_argument( "--set-name", action=ActionSetName, default=SUPPRESS, help="Select D-Bus interface to adjust" ) generate_from_file_parser.add_argument( 'filenames', type=Path, nargs='+', help="Paths to interface XML introspection files" ) generate_from_connection.add_argument( 'connection_name', help=( 'Name of the service connection to extract introspection. ' "For example, 'org.freedesktop.systemd1' for systemd." ) ) generate_from_connection.add_argument( 'object_paths', nargs='+', help=( 'Object paths that the introspection will be extracted. ' 'One or more.' ) ) generate_from_connection.add_argument( '--system', help='Use system D-Bus instead of session.', action='store_true', ) args_dict = vars(main_arg_parser.parse_args(args)) func = args_dict.pop("func") func(**args_dict) if __name__ == "__main__": generator_main() python-sdbus-0.14.0/src/sdbus/autodoc.py000066400000000000000000000107051477456016000201670ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from sphinx.ext.autodoc import AttributeDocumenter, MethodDocumenter from .dbus_proxy_async_method import DbusMethodAsync from .dbus_proxy_async_property import DbusPropertyAsync from .dbus_proxy_async_signal import DbusSignalAsync if TYPE_CHECKING: from typing import Any from sphinx.application import Sphinx class DbusMethodDocumenter(MethodDocumenter): objtype = 'DbusMethodAsyncClassBind' directivetype = 'method' priority = 100 + MethodDocumenter.priority @classmethod def can_document_member(cls, member: Any, *args: Any) -> bool: return isinstance(member, DbusMethodAsync) def import_object(self, raiseerror: bool = False) -> bool: self.objpath.append('original_method') ret = super().import_object(raiseerror) self.objpath.pop() return ret def add_content(self, *args: Any, **kwargs: Any, ) -> None: source_name = self.get_sourcename() self.add_line('', source_name) self.add_line('**D-Bus Method**', source_name) self.add_line('', source_name) super().add_content(*args, **kwargs) class DbusPropertyDocumenter(AttributeDocumenter): objtype = 'DbusPropertyAsync' directivetype = 'attribute' priority = 100 + AttributeDocumenter.priority @classmethod def can_document_member(cls, member: Any, *args: Any) -> bool: return isinstance(member, DbusPropertyAsync) def add_content(self, *args: Any, **kwargs: Any, ) -> None: assert isinstance(self.object, DbusPropertyAsync) python_type = self.object.property_getter.__annotations__['return'] dbus_type = self.object.property_signature source_name = self.get_sourcename() self.add_line('', source_name) self.add_line('**D-Bus property**', source_name) self.add_line('', source_name) self.add_line( f"**Python type**: *{python_type}*", source_name ) self.add_line('', source_name) self.add_line( f"**D-Bus type**: {dbus_type}", source_name ) self.add_line('', source_name) super().add_content(*args, **kwargs) class DbusSignalDocumenter(AttributeDocumenter): objtype = 'DbusSignalAsync' directivetype = 'attribute' priority = 100 + AttributeDocumenter.priority @classmethod def can_document_member(cls, member: Any, *args: Any) -> bool: return isinstance(member, DbusSignalAsync) def add_content(self, *args: Any, **kwargs: Any, ) -> None: assert isinstance(self.object, DbusSignalAsync) python_type = self.object.__annotations__['return'] dbus_type = self.object.signal_signature source_name = self.get_sourcename() self.add_line('', source_name) self.add_line('**D-Bus signal**', source_name) self.add_line('', source_name) self.add_line( f"**Python type**: *{python_type}*", source_name ) self.add_line('', source_name) self.add_line( f"**D-Bus type**: {dbus_type}", source_name ) self.add_line('', source_name) super().add_content(*args, **kwargs) def setup(app: Sphinx) -> dict[str, bool]: app.setup_extension('sphinx.ext.autodoc') app.add_autodocumenter(DbusMethodDocumenter) app.add_autodocumenter(DbusPropertyDocumenter) app.add_autodocumenter(DbusSignalDocumenter) return { 'parallel_read_safe': True, 'parallel_write_safe': True, } python-sdbus-0.14.0/src/sdbus/dbus_common_elements.py000066400000000000000000000255051477456016000227360ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from inspect import getfullargspec from typing import TYPE_CHECKING, Generic, TypeVar from .dbus_common_funcs import ( _is_property_flags_correct, snake_case_to_camel_case, ) from .default_bus import get_default_bus from .sd_bus_internals import is_interface_name_valid, is_member_name_valid if TYPE_CHECKING: from collections.abc import Callable, Sequence from types import FunctionType from typing import Any, Optional SelfMeta = TypeVar('SelfMeta', bound="DbusInterfaceMetaCommon") from .sd_bus_internals import SdBus, SdBusInterface T = TypeVar('T') class DbusMemberCommon: interface_name: str serving_enabled: bool class DbusMemberAsync(DbusMemberCommon): ... class DbusMemberSync(DbusMemberCommon): ... class DbusInterfaceMetaCommon(type): def __new__(cls: type[SelfMeta], name: str, bases: tuple[type, ...], namespace: dict[str, Any], interface_name: Optional[str] = None, serving_enabled: bool = True, ) -> SelfMeta: if interface_name is not None: try: assert is_interface_name_valid(interface_name), ( f"Invalid interface name: \"{interface_name}\"; " 'Interface names must be composed of 2 or more elements ' 'separated by a dot \'.\' character. All elements must ' 'contain at least one character, consist of ASCII ' 'characters, first character must not be digit and ' 'length must not exceed 255 characters.' ) except NotImplementedError: ... for attr_name, attr in namespace.items(): if not isinstance(attr, DbusMemberCommon): continue # TODO: Fix async metaclass copying all methods if hasattr(attr, "interface_name"): continue if interface_name is None: raise TypeError( f"Defined D-Bus element {attr_name!r} without " f"interface name in the class {name!r}." ) attr.interface_name = interface_name attr.serving_enabled = serving_enabled new_cls = super().__new__(cls, name, bases, namespace) return new_cls MEMBER_NAME_REQUIREMENTS = ( 'Member name must only contain ASCII characters, ' 'cannot start with digit, ' 'must not contain dot \'.\' and be between 1 ' 'and 255 characters in length.' ) class DbusMethodCommon(DbusMemberCommon): def __init__( self, original_method: FunctionType, method_name: Optional[str], input_signature: str, input_args_names: Optional[Sequence[str]], result_signature: str, result_args_names: Optional[Sequence[str]], flags: int): assert not isinstance(input_args_names, str), ( "Passed a string as input args" " names. Did you forget to put" " it in to a tuple ('string', ) ?") if method_name is None: method_name = snake_case_to_camel_case(original_method.__name__) try: assert is_member_name_valid(method_name), ( f"Invalid method name: \"{method_name}\"; " f"{MEMBER_NAME_REQUIREMENTS}" ) except NotImplementedError: ... super().__init__() self.original_method = original_method self.args_spec = getfullargspec(original_method) self.args_names = self.args_spec.args[1:] # 1: because of self self.num_of_args = len(self.args_names) self.args_defaults = ( self.args_spec.defaults if self.args_spec.defaults is not None else ()) self.default_args_start_at = self.num_of_args - len(self.args_defaults) self.method_name = method_name self.input_signature = input_signature self.input_args_names: Sequence[str] = () if input_args_names is not None: assert not any(' ' in x for x in input_args_names), ( "Can't have spaces in argument input names" f"Args: {input_args_names}") self.input_args_names = input_args_names elif result_args_names is not None: self.input_args_names = self.args_names self.result_signature = result_signature self.result_args_names: Sequence[str] = () if result_args_names is not None: assert not any(' ' in x for x in result_args_names), ( "Can't have spaces in argument result names." f"Args: {result_args_names}") self.result_args_names = result_args_names self.flags = flags self.__doc__ = original_method.__doc__ def _rebuild_args( self, function: FunctionType, *args: Any, **kwargs: dict[str, Any]) -> list[Any]: # 3 types of arguments # *args - should be passed directly # **kwargs - should be put in a proper order # defaults - should be retrieved and put in proper order # Strategy: # Iterate over arg names # Use: # 1. Arg # 2. Kwarg # 3. Default # a, b, c, d, e # ^ defaults start here # 5 - 3 = [2] # ^ total args # ^ number of default args # First arg that supports default is # (total args - number of default args) passed_args_iter = iter(args) default_args_iter = iter(self.args_defaults) new_args_list: list[Any] = [] for i, a_name in enumerate(self.args_spec.args[1:]): try: next_arg = next(passed_args_iter) except StopIteration: next_arg = None if i >= self.default_args_start_at: next_default_arg = next(default_args_iter) else: next_default_arg = None next_kwarg = kwargs.get(a_name) if next_arg is not None: new_args_list.append(next_arg) elif next_kwarg is not None: new_args_list.append(next_kwarg) elif next_default_arg is not None: new_args_list.append(next_default_arg) else: raise TypeError('Could not flatten the args') return new_args_list class DbusPropertyCommon(DbusMemberCommon): def __init__(self, property_name: Optional[str], property_signature: str, flags: int, original_method: FunctionType): if property_name is None: property_name = snake_case_to_camel_case(original_method.__name__) try: assert is_member_name_valid(property_name), ( f"Invalid property name: \"{property_name}\"; " f"{MEMBER_NAME_REQUIREMENTS}" ) except NotImplementedError: ... assert _is_property_flags_correct(flags), ( 'Incorrect number of Property flags. ' 'Only one of DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, ' 'DbusPropertyEmitsInvalidationFlag or DbusPropertyExplicitFlag ' 'is allowed.' ) super().__init__() self.property_name: str = property_name self.property_signature = property_signature self.flags = flags class DbusSignalCommon(DbusMemberCommon): def __init__(self, signal_name: Optional[str], signal_signature: str, args_names: Sequence[str], flags: int, original_method: FunctionType): if signal_name is None: signal_name = snake_case_to_camel_case(original_method.__name__) try: assert is_member_name_valid(signal_name), ( f"Invalid signal name: \"{signal_name}\"; " f"{MEMBER_NAME_REQUIREMENTS}" ) except NotImplementedError: ... super().__init__() self.signal_name = signal_name self.signal_signature = signal_signature self.args_names = args_names self.flags = flags self.__doc__ = original_method.__doc__ self.__annotations__ = original_method.__annotations__ class DbusBoundAsync: ... class DbusBoundSync: ... class DbusMethodOverride(Generic[T]): def __init__(self, override_method: T): self.override_method = override_method class DbusPropertyOverride(Generic[T]): def __init__(self, getter_override: Callable[[Any], T]): self.getter_override = getter_override self.setter_override: Optional[Callable[[Any, T], None]] = None self.is_setter_public = True def setter(self, new_setter: Optional[Callable[[Any, T], None]]) -> None: self.setter_override = new_setter def setter_private( self, new_setter: Optional[Callable[[Any, T], None]], ) -> None: self.setter_override = new_setter self.is_setter_public = False class DbusRemoteObjectMeta: def __init__( self, service_name: str, object_path: str, bus: Optional[SdBus] = None, ): self.service_name = service_name self.object_path = object_path self.attached_bus = ( bus if bus is not None else get_default_bus() ) class DbusLocalObjectMeta: def __init__(self) -> None: self.activated_interfaces: list[SdBusInterface] = [] self.serving_object_path: Optional[str] = None self.attached_bus: Optional[SdBus] = None class DbusClassMeta: def __init__( self, interface_name: str, serving_enabled: bool, ) -> None: self.interface_name = interface_name self.serving_enabled = serving_enabled self.dbus_member_to_python_attr: dict[str, str] = {} self.python_attr_to_dbus_member: dict[str, str] = {} python-sdbus-0.14.0/src/sdbus/dbus_common_funcs.py000066400000000000000000000062211477456016000222320ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import get_running_loop from typing import TYPE_CHECKING from .sd_bus_internals import ( DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, DbusPropertyEmitsInvalidationFlag, DbusPropertyExplicitFlag, ) if TYPE_CHECKING: from collections.abc import Iterator, Mapping from typing import Any, Literal PROPERTY_FLAGS_MASK = ( DbusPropertyConstFlag | DbusPropertyEmitsChangeFlag | DbusPropertyEmitsInvalidationFlag | DbusPropertyExplicitFlag ) def count_bits(i: int) -> int: return bin(i).count('1') def _is_property_flags_correct(flags: int) -> bool: num_of_flag_bits = count_bits(PROPERTY_FLAGS_MASK & flags) return (0 <= num_of_flag_bits <= 1) def _snake_case_to_camel_case_gen(snake: str) -> Iterator[str]: char_iter = iter(snake) # Name starting with upper case letter try: first_char = next(char_iter) except StopIteration: return yield first_char.upper() upper_next_one = False for c in char_iter: # Every under score remove and uppercase next one if c != '_': if upper_next_one: yield c.upper() upper_next_one = False else: yield c else: upper_next_one = True def snake_case_to_camel_case(snake: str) -> str: return "".join(_snake_case_to_camel_case_gen(snake)) def _check_sync_in_async_env() -> bool: try: get_running_loop() return False except RuntimeError: return True def _parse_properties_vardict( properties_name_map: Mapping[str, str], properties_vardict: dict[str, tuple[str, Any]], on_unknown_member: Literal['error', 'ignore', 'reuse'], ) -> dict[str, Any]: properties_translated: dict[str, Any] = {} for member_name, variant in properties_vardict.items(): try: python_name = properties_name_map[member_name] except KeyError: if on_unknown_member == 'error': raise elif on_unknown_member == 'ignore': continue elif on_unknown_member == 'reuse': python_name = member_name else: raise ValueError properties_translated[python_name] = variant[1] return properties_translated python-sdbus-0.14.0/src/sdbus/dbus_exceptions.py000066400000000000000000000240771477456016000217360ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from .sd_bus_internals import ( SdBusBaseError, add_exception_mapping, map_exception_to_dbus_error, ) if TYPE_CHECKING: from typing import Any class DbusErrorMeta(type): def __new__( cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], ) -> DbusErrorMeta: dbus_error_name = namespace.get('dbus_error_name') if dbus_error_name is None: raise TypeError('D-Bus error name not passed') new_cls = super().__new__(cls, name, bases, namespace) assert issubclass(new_cls, Exception), ( f"New class {new_cls} is not an Exception but {bases}." ) add_exception_mapping(new_cls) return new_cls class DbusFailedError(SdBusBaseError, metaclass=DbusErrorMeta): dbus_error_name: str = 'org.freedesktop.DBus.Error.Failed' class DbusNoMemoryError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NoMemory' class DbusServiceUnknownError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.ServiceUnknown' class DbusNameHasNoOwnerError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NameHasNoOwner' class DbusNoReplyError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NoReply' class DbusIOError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.IOError' class DbusBadAddressError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.BadAddress' class DbusNotSupportedError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NotSupported' class DbusLimitsExceededError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.LimitsExceeded' class DbusAccessDeniedError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.AccessDenied' class DbusAuthFailedError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.AuthFailed' class DbusNoServerError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NoServer' class DbusTimeoutError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.Timeout' class DbusNoNetworkError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NoNetwork' class DbusAddressInUseError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.AddressInUse' class DbusDisconnectedError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.Disconnected' class DbusInvalidArgsError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs' class DbusFileNotFoundError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.FileNotFound' class DbusFileExistsError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.FileExists' class DbusUnknownMethodError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.UnknownMethod' class DbusUnknownObjectError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.UnknownObject' class DbusUnknownInterfaceError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.UnknownInterface' class DbusUnknownPropertyError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.UnknownProperty' class DbusPropertyReadOnlyError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.PropertyReadOnly' class DbusUnixProcessIdUnknownError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.UnixProcessIdUnknown' class DbusInvalidSignatureError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.InvalidSignature' class DbusInvalidFileContentError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.InvalidFileContent' class DbusInconsistentMessageError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.InconsistentMessage' class DbusMatchRuleNotFound(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.MatchRuleNotFound' class DbusMatchRuleInvalidError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.MatchRuleInvalid' class DbusInteractiveAuthorizationRequiredError(DbusFailedError): dbus_error_name = ('org.freedesktop.DBus.Error' '.InteractiveAuthorizationRequired') map_exception_to_dbus_error(AssertionError, "org.python.Error.AssertionError") map_exception_to_dbus_error(AttributeError, "org.python.Error.AttributeError") map_exception_to_dbus_error(BufferError, "org.python.Error.BufferError") map_exception_to_dbus_error(EOFError, "org.python.Error.EOFError") map_exception_to_dbus_error(ImportError, "org.python.Error.ImportError") map_exception_to_dbus_error(ModuleNotFoundError, "org.python.Error.ModuleNotFoundError") map_exception_to_dbus_error(LookupError, "org.python.Error.LookupError") map_exception_to_dbus_error(IndexError, "org.python.Error.IndexError") map_exception_to_dbus_error(KeyError, "org.python.Error.KeyError") map_exception_to_dbus_error(NameError, "org.python.Error.NameError") map_exception_to_dbus_error(NotImplementedError, "org.python.Error.NotImplementedError") map_exception_to_dbus_error(OSError, "org.python.Error.OSError") map_exception_to_dbus_error(RecursionError, "org.python.Error.RecursionError") map_exception_to_dbus_error(ReferenceError, "org.python.Error.ReferenceError") map_exception_to_dbus_error(RuntimeError, "org.python.Error.RuntimeError") map_exception_to_dbus_error(SyntaxError, "org.python.Error.SyntaxError") map_exception_to_dbus_error(IndentationError, "org.python.Error.IndentationError") map_exception_to_dbus_error(TabError, "org.python.Error.TabError") map_exception_to_dbus_error(SystemError, "org.python.Error.SystemError") map_exception_to_dbus_error(TypeError, "org.python.Error.TypeError") map_exception_to_dbus_error(UnboundLocalError, "org.python.Error.UnboundLocalError") map_exception_to_dbus_error(UnicodeError, "org.python.Error.UnicodeError") map_exception_to_dbus_error(UnicodeEncodeError, "org.python.Error.UnicodeEncodeError") map_exception_to_dbus_error(UnicodeDecodeError, "org.python.Error.UnicodeDecodeError") map_exception_to_dbus_error(UnicodeTranslateError, "org.python.Error.UnicodeTranslateError") map_exception_to_dbus_error(ValueError, "org.python.Error.ValueError") map_exception_to_dbus_error(EnvironmentError, "org.python.Error.EnvironmentError") map_exception_to_dbus_error(IOError, "org.python.Error.IOError") map_exception_to_dbus_error(BlockingIOError, "org.python.Error.BlockingIOError") map_exception_to_dbus_error(ChildProcessError, "org.python.Error.ChildProcessError") map_exception_to_dbus_error(ConnectionError, "org.python.Error.ConnectionError") map_exception_to_dbus_error(BrokenPipeError, "org.python.Error.BrokenPipeError") map_exception_to_dbus_error(ConnectionAbortedError, "org.python.Error.ConnectionAbortedError") map_exception_to_dbus_error(ConnectionRefusedError, "org.python.Error.ConnectionRefusedError") map_exception_to_dbus_error(ConnectionResetError, "org.python.Error.ConnectionResetError") map_exception_to_dbus_error(FileExistsError, "org.python.Error.FileExistsError") map_exception_to_dbus_error(FileNotFoundError, "org.python.Error.FileNotFoundError") map_exception_to_dbus_error(InterruptedError, "org.python.Error.InterruptedError") map_exception_to_dbus_error(IsADirectoryError, "org.python.Error.IsADirectoryError") map_exception_to_dbus_error(NotADirectoryError, "org.python.Error.NotADirectoryError") map_exception_to_dbus_error(PermissionError, "org.python.Error.PermissionError") map_exception_to_dbus_error(ProcessLookupError, "org.python.Error.ProcessLookupError") map_exception_to_dbus_error(TimeoutError, "org.python.Error.TimeoutError") map_exception_to_dbus_error(ArithmeticError, "org.python.Error.ArithmeticError") map_exception_to_dbus_error(FloatingPointError, "org.python.Error.FloatingPointError") map_exception_to_dbus_error(OverflowError, "org.python.Error.OverflowError") map_exception_to_dbus_error(ZeroDivisionError, "org.python.Error.ZeroDivisionError") python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_interface_base.py000066400000000000000000000372651477456016000251500ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from collections.abc import Callable from copy import copy from itertools import chain from types import MethodType from typing import TYPE_CHECKING, Any, cast from warnings import warn from weakref import WeakKeyDictionary, WeakValueDictionary from .dbus_common_elements import ( DbusClassMeta, DbusInterfaceMetaCommon, DbusLocalObjectMeta, DbusMemberAsync, DbusMemberCommon, DbusMemberSync, DbusMethodOverride, DbusPropertyOverride, DbusRemoteObjectMeta, ) from .dbus_proxy_async_method import DbusLocalMethodAsync, DbusMethodAsync from .dbus_proxy_async_property import ( DbusLocalPropertyAsync, DbusPropertyAsync, ) from .dbus_proxy_async_signal import DbusLocalSignalAsync, DbusSignalAsync from .default_bus import get_default_bus from .sd_bus_internals import SdBusInterface if TYPE_CHECKING: from collections.abc import Iterable, Iterator from typing import Optional, TypeVar, Union from .sd_bus_internals import SdBus, SdBusSlot T = TypeVar('T') Self = TypeVar('Self', bound="DbusInterfaceBaseAsync") DbusOverride = Union[DbusMethodOverride[T], DbusPropertyOverride[T]] DBUS_CLASS_TO_META: WeakKeyDictionary[ type, DbusClassMeta] = WeakKeyDictionary() DBUS_INTERFACE_NAME_TO_CLASS: WeakValueDictionary[ str, DbusInterfaceMetaAsync] = WeakValueDictionary() class DbusInterfaceMetaAsync(DbusInterfaceMetaCommon): @staticmethod def _process_dbus_method_override( override_attr_name: str, override: DbusMethodOverride[T], mro_dbus_elements: dict[str, DbusMemberAsync], ) -> DbusMethodAsync: try: original_method = mro_dbus_elements[override_attr_name] except KeyError: raise ValueError( f"No D-Bus method {override_attr_name!r} found " f"to override." ) if not isinstance(original_method, DbusMethodAsync): raise TypeError( f"Expected {DbusMethodAsync!r} got {original_method!r} " f"under name {override_attr_name!r}" ) new_method = copy(original_method) new_method.original_method = cast(MethodType, override.override_method) return new_method @staticmethod def _process_dbus_property_override( override_attr_name: str, override: DbusPropertyOverride[T], mro_dbus_elements: dict[str, DbusMemberAsync], ) -> DbusPropertyAsync[Any]: try: original_property = mro_dbus_elements[override_attr_name] except KeyError: raise ValueError( f"No D-Bus property {override_attr_name!r} found " f"to override." ) if not isinstance(original_property, DbusPropertyAsync): raise TypeError( f"Expected {DbusMethodAsync!r} got {original_property!r} " f"under name {override_attr_name!r}" ) new_property = copy(original_property) new_property.property_getter = cast( Callable[[DbusInterfaceBaseAsync], Any], override.getter_override ) if override.setter_override is not None: new_property.property_setter = override.setter_override new_property.property_setter_is_public = override.is_setter_public return new_property @classmethod def _check_collisions( cls, new_class_name: str, namespace: dict[str, Any], mro_dbus_elements: dict[str, DbusMemberAsync], ) -> None: possible_collisions = namespace.keys() & mro_dbus_elements.keys() new_overrides: dict[str, DbusMemberAsync] = {} for attr_name, attr in namespace.items(): if isinstance(attr, DbusMethodOverride): new_overrides[attr_name] = cls._process_dbus_method_override( attr_name, attr, mro_dbus_elements, ) possible_collisions.remove(attr_name) elif isinstance(attr, DbusPropertyOverride): new_overrides[attr_name] = cls._process_dbus_property_override( attr_name, attr, mro_dbus_elements, ) possible_collisions.remove(attr_name) else: continue if possible_collisions: raise ValueError( f"Interface {new_class_name!r} redefines reserved " f"D-Bus attribute names: {possible_collisions!r}" ) namespace.update(new_overrides) @staticmethod def _extract_dbus_elements( dbus_class: type, dbus_meta: DbusClassMeta, ) -> dict[str, DbusMemberAsync]: dbus_elements_map: dict[str, DbusMemberAsync] = {} for attr_name in dbus_meta.python_attr_to_dbus_member.keys(): dbus_element = dbus_class.__dict__.get(attr_name) if not isinstance(dbus_element, DbusMemberAsync): raise TypeError( f"Expected async D-Bus element, got {dbus_element!r} " f"in class {dbus_class!r}" ) dbus_elements_map[attr_name] = dbus_element return dbus_elements_map @classmethod def _map_mro_dbus_elements( cls, new_class_name: str, base_classes: Iterable[type], ) -> dict[str, DbusMemberAsync]: all_python_dbus_map: dict[str, DbusMemberAsync] = {} possible_collisions: set[str] = set() for c in base_classes: dbus_meta = DBUS_CLASS_TO_META.get(c) if dbus_meta is None: continue base_dbus_elements = cls._extract_dbus_elements(c, dbus_meta) possible_collisions.update( base_dbus_elements.keys() & all_python_dbus_map.keys() ) all_python_dbus_map.update( base_dbus_elements ) if possible_collisions: raise ValueError( f"Interface {new_class_name!r} has a reserved D-Bus " f"attribute name collision: {possible_collisions!r}" ) return all_python_dbus_map @staticmethod def _map_dbus_elements( attr_name: str, attr: Any, meta: DbusClassMeta, interface_name: str, ) -> None: if not isinstance(attr, DbusMemberCommon): return if isinstance(attr, DbusMemberSync): raise TypeError( "Can't mix blocking methods in " f"async interface: {attr_name!r}" ) if attr.interface_name != interface_name: return if isinstance(attr, DbusMethodAsync): meta.dbus_member_to_python_attr[attr.method_name] = attr_name meta.python_attr_to_dbus_member[attr_name] = attr.method_name elif isinstance(attr, DbusPropertyAsync): meta.dbus_member_to_python_attr[attr.property_name] = attr_name meta.python_attr_to_dbus_member[attr_name] = attr.property_name elif isinstance(attr, DbusSignalAsync): meta.dbus_member_to_python_attr[attr.signal_name] = attr_name meta.python_attr_to_dbus_member[attr_name] = attr.signal_name else: raise TypeError(f"Unknown D-Bus element: {attr!r}") def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], interface_name: Optional[str] = None, serving_enabled: bool = True, ) -> DbusInterfaceMetaAsync: if interface_name in DBUS_INTERFACE_NAME_TO_CLASS: raise ValueError( f"D-Bus interface of the name {interface_name!r} was " "already created." ) all_mro_bases: set[type[Any]] = set( chain.from_iterable(c.__mro__ for c in bases) ) reserved_dbus_map = cls._map_mro_dbus_elements( name, all_mro_bases, ) cls._check_collisions(name, namespace, reserved_dbus_map) new_cls = super().__new__( cls, name, bases, namespace, interface_name, serving_enabled, ) if interface_name is not None: dbus_class_meta = DbusClassMeta(interface_name, serving_enabled) DBUS_CLASS_TO_META[new_cls] = dbus_class_meta DBUS_INTERFACE_NAME_TO_CLASS[interface_name] = new_cls for attr_name, attr in namespace.items(): cls._map_dbus_elements( attr_name, attr, dbus_class_meta, interface_name, ) return new_cls class DbusInterfaceBaseAsync(metaclass=DbusInterfaceMetaAsync): def __init__(self) -> None: self._dbus: Union[ DbusRemoteObjectMeta, DbusLocalObjectMeta] = DbusLocalObjectMeta() @classmethod def _dbus_iter_interfaces_meta( cls, ) -> Iterator[tuple[str, DbusClassMeta]]: for base in cls.__mro__: meta = DBUS_CLASS_TO_META.get(base) if meta is None: continue yield meta.interface_name, meta async def start_serving(self, object_path: str, bus: Optional[SdBus] = None, ) -> None: warn("start_serving is deprecated in favor of export_to_dbus", DeprecationWarning) self.export_to_dbus(object_path, bus) def _dbus_on_no_members_exported(self) -> None: raise ValueError("No D-Bus interfaces were exported") def export_to_dbus( self, object_path: str, bus: Optional[SdBus] = None, ) -> DbusExportHandle: local_object_meta = self._dbus if isinstance(local_object_meta, DbusRemoteObjectMeta): raise RuntimeError("Cannot export D-Bus proxies.") # TODO: Being able to serve multiple buses and object if local_object_meta.attached_bus is not None: raise RuntimeError( "Object already exported. " "This limitation should be fixed in future version." ) if bus is None: bus = get_default_bus() local_object_meta.attached_bus = bus local_object_meta.serving_object_path = object_path for interface_name, meta in self._dbus_iter_interfaces_meta(): if not meta.serving_enabled: continue new_interface = SdBusInterface() for python_attr, dbus_member in ( meta.python_attr_to_dbus_member.items() ): dbus_something = getattr(self, python_attr) if isinstance(dbus_something, DbusLocalMethodAsync): new_interface.add_method( dbus_something.dbus_method.method_name, dbus_something.dbus_method.input_signature, dbus_something.dbus_method.input_args_names, dbus_something.dbus_method.result_signature, dbus_something.dbus_method.result_args_names, dbus_something.dbus_method.flags, dbus_something._dbus_reply_call, ) elif isinstance(dbus_something, DbusLocalPropertyAsync): getter = dbus_something._dbus_reply_get dbus_property = dbus_something.dbus_property if ( dbus_property.property_setter is not None and dbus_property.property_setter_is_public ): setter = dbus_something._dbus_reply_set else: setter = None new_interface.add_property( dbus_property.property_name, dbus_property.property_signature, getter, setter, dbus_property.flags, ) elif isinstance(dbus_something, DbusLocalSignalAsync): new_interface.add_signal( dbus_something.dbus_signal.signal_name, dbus_something.dbus_signal.signal_signature, dbus_something.dbus_signal.args_names, dbus_something.dbus_signal.flags, ) else: raise TypeError( "Expected D-Bus element, got: {dbus_something!r}" ) bus.add_interface(new_interface, object_path, interface_name) local_object_meta.activated_interfaces.append(new_interface) if not local_object_meta.activated_interfaces: self._dbus_on_no_members_exported() return DbusExportHandle(local_object_meta) def _connect( self, service_name: str, object_path: str, bus: Optional[SdBus] = None, ) -> None: self._proxify( service_name, object_path, bus, ) def _proxify( self, service_name: str, object_path: str, bus: Optional[SdBus] = None, ) -> None: self._dbus = DbusRemoteObjectMeta( service_name, object_path, bus, ) @classmethod def new_connect( cls: type[Self], service_name: str, object_path: str, bus: Optional[SdBus] = None, ) -> Self: warn( ("new_connect is deprecated in favor of equivalent new_proxy." "Will be removed in version 1.0.0"), DeprecationWarning, ) new_object = cls.__new__(cls) new_object._proxify(service_name, object_path, bus) return new_object @classmethod def new_proxy( cls: type[Self], service_name: str, object_path: str, bus: Optional[SdBus] = None, ) -> Self: new_object = cls.__new__(cls) new_object._proxify(service_name, object_path, bus) return new_object class DbusExportHandle: def __init__(self, local_meta: DbusLocalObjectMeta): self._dbus_slots: list[SdBusSlot] = [ i.slot for i in local_meta.activated_interfaces if i.slot is not None ] async def __aenter__(self) -> DbusExportHandle: return self def __enter__(self) -> DbusExportHandle: return self def __exit__( self, exc_type: Any, exc_value: Any, traceback: Any, ) -> None: self.stop() async def __aexit__( self, exc_type: Any, exc_value: Any, traceback: Any, ) -> None: self.stop() def stop(self) -> None: for slot in self._dbus_slots: slot.close() python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_interfaces.py000066400000000000000000000064261477456016000243340ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from .dbus_common_funcs import _parse_properties_vardict from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync from .dbus_proxy_async_method import dbus_method_async from .dbus_proxy_async_signal import dbus_signal_async if TYPE_CHECKING: from typing import Any, Literal DBUS_PROPERTIES_CHANGED_TYPING = ( tuple[ str, dict[str, tuple[str, Any]], list[str], ] ) class DbusPeerInterfaceAsync( DbusInterfaceBaseAsync, interface_name='org.freedesktop.DBus.Peer', serving_enabled=False, ): @dbus_method_async(method_name='Ping') async def dbus_ping(self) -> None: raise NotImplementedError @dbus_method_async(method_name='GetMachineId') async def dbus_machine_id(self) -> str: raise NotImplementedError class DbusIntrospectableAsync( DbusInterfaceBaseAsync, interface_name='org.freedesktop.DBus.Introspectable', serving_enabled=False, ): @dbus_method_async(method_name='Introspect') async def dbus_introspect(self) -> str: raise NotImplementedError class DbusPropertiesInterfaceAsync( DbusInterfaceBaseAsync, interface_name='org.freedesktop.DBus.Properties', serving_enabled=False, ): @dbus_signal_async('sa{sv}as') def properties_changed(self) -> DBUS_PROPERTIES_CHANGED_TYPING: raise NotImplementedError @dbus_method_async('s', 'a{sv}', method_name='GetAll') async def _properties_get_all( self, interface_name: str) -> dict[str, tuple[str, Any]]: raise NotImplementedError async def properties_get_all_dict( self, on_unknown_member: Literal['error', 'ignore', 'reuse'] = 'error', ) -> dict[str, Any]: properties: dict[str, Any] = {} for interface_name, meta in self._dbus_iter_interfaces_meta(): if not meta.serving_enabled: continue dbus_properties_data = await self._properties_get_all( interface_name) properties.update( _parse_properties_vardict( meta.dbus_member_to_python_attr, dbus_properties_data, on_unknown_member, ) ) return properties class DbusInterfaceCommonAsync( DbusPeerInterfaceAsync, DbusPropertiesInterfaceAsync, DbusIntrospectableAsync): ... python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_method.py000066400000000000000000000204671477456016000234720ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from contextvars import ContextVar, copy_context from inspect import iscoroutinefunction from types import FunctionType from typing import TYPE_CHECKING, cast, overload from weakref import ref as weak_ref from .dbus_common_elements import ( DbusBoundAsync, DbusMemberAsync, DbusMethodCommon, DbusMethodOverride, DbusRemoteObjectMeta, ) from .dbus_exceptions import DbusFailedError from .sd_bus_internals import EXCEPTION_TO_DBUS_ERROR, DbusNoReplyFlag if TYPE_CHECKING: from collections.abc import Callable, Sequence from typing import Any, Optional, TypeVar, Union from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync from .sd_bus_internals import SdBusMessage T = TypeVar('T') else: T = None CURRENT_MESSAGE: ContextVar[SdBusMessage] = ContextVar('CURRENT_MESSAGE') def get_current_message() -> SdBusMessage: return CURRENT_MESSAGE.get() class DbusMethodAsync(DbusMethodCommon, DbusMemberAsync): @overload def __get__( self, obj: None, obj_class: type[DbusInterfaceBaseAsync], ) -> DbusMethodAsync: ... @overload def __get__( self, obj: DbusInterfaceBaseAsync, obj_class: type[DbusInterfaceBaseAsync], ) -> Callable[..., Any]: ... def __get__( self, obj: Optional[DbusInterfaceBaseAsync], obj_class: Optional[type[DbusInterfaceBaseAsync]] = None, ) -> Union[Callable[..., Any], DbusMethodAsync]: if obj is not None: dbus_meta = obj._dbus if isinstance(dbus_meta, DbusRemoteObjectMeta): return DbusProxyMethodAsync(self, dbus_meta) else: return DbusLocalMethodAsync(self, obj) else: return self class DbusBoundMethodAsyncBase(DbusBoundAsync): def __call__(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError class DbusProxyMethodAsync(DbusBoundMethodAsyncBase): def __init__( self, dbus_method: DbusMethodAsync, proxy_meta: DbusRemoteObjectMeta, ): self.dbus_method = dbus_method self.proxy_meta = proxy_meta self.__doc__ = dbus_method.__doc__ async def _dbus_async_call(self, call_message: SdBusMessage) -> Any: bus = self.proxy_meta.attached_bus reply_message = await bus.call_async(call_message) return reply_message.get_contents() @staticmethod async def _no_reply() -> None: return None def __call__(self, *args: Any, **kwargs: Any) -> Any: bus = self.proxy_meta.attached_bus dbus_method = self.dbus_method new_call_message = bus.new_method_call_message( self.proxy_meta.service_name, self.proxy_meta.object_path, dbus_method.interface_name, dbus_method.method_name, ) if len(args) == dbus_method.num_of_args: assert not kwargs, ( "Passed more arguments than method supports" f"Extra args: {kwargs}") rebuilt_args: Sequence[Any] = args else: rebuilt_args = dbus_method._rebuild_args( dbus_method.original_method, *args, **kwargs) if rebuilt_args: new_call_message.append_data( dbus_method.input_signature, *rebuilt_args) if dbus_method.flags & DbusNoReplyFlag: new_call_message.expect_reply = False new_call_message.send() return self._no_reply() return self._dbus_async_call(new_call_message) class DbusLocalMethodAsync(DbusBoundMethodAsyncBase): def __init__( self, dbus_method: DbusMethodAsync, local_object: DbusInterfaceBaseAsync, ): self.dbus_method = dbus_method self.local_object_ref = weak_ref(local_object) self.__doc__ = dbus_method.__doc__ def __call__(self, *args: Any, **kwargs: Any) -> Any: local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") return self.dbus_method.original_method(local_object, *args, **kwargs) async def _dbus_reply_call_method( self, request_message: SdBusMessage, local_object: DbusInterfaceBaseAsync, ) -> Any: local_method = self.dbus_method.original_method.__get__( local_object, None) CURRENT_MESSAGE.set(request_message) return await local_method(*request_message.parse_to_tuple()) async def _dbus_reply_call( self, request_message: SdBusMessage ) -> None: local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") call_context = copy_context() try: reply_data = await call_context.run( self._dbus_reply_call_method, request_message, local_object, ) except Exception as e: if not request_message.expect_reply: return dbus_error = EXCEPTION_TO_DBUS_ERROR.get(type(e)) if dbus_error is None: dbus_error = DbusFailedError.dbus_error_name error_message = request_message.create_error_reply( dbus_error, str(e.args[0]) if e.args else "", ) error_message.send() return if not request_message.expect_reply: return reply_message = request_message.create_reply() if isinstance(reply_data, tuple): try: reply_message.append_data( self.dbus_method.result_signature, *reply_data) except TypeError: # In case of single struct result type # We can't figure out if return is multiple values # or a tuple reply_message.append_data( self.dbus_method.result_signature, reply_data) elif reply_data is not None: reply_message.append_data( self.dbus_method.result_signature, reply_data) reply_message.send() def dbus_method_async( input_signature: str = "", result_signature: str = "", flags: int = 0, result_args_names: Optional[Sequence[str]] = None, input_args_names: Optional[Sequence[str]] = None, method_name: Optional[str] = None, ) -> Callable[[T], T]: assert not isinstance(input_signature, FunctionType), ( "Passed function to decorator directly. " "Did you forget () round brackets?" ) def dbus_method_decorator(original_method: T) -> T: assert isinstance(original_method, FunctionType) assert iscoroutinefunction(original_method), ( "Expected coroutine function. ", "Maybe you forgot 'async' keyword?", ) new_wrapper = DbusMethodAsync( original_method=original_method, method_name=method_name, input_signature=input_signature, result_signature=result_signature, result_args_names=result_args_names, input_args_names=input_args_names, flags=flags, ) return cast(T, new_wrapper) return dbus_method_decorator def dbus_method_async_override() -> Callable[[T], T]: def new_decorator( new_function: T) -> T: return cast(T, DbusMethodOverride(new_function)) return new_decorator python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_object_manager.py000066400000000000000000000105111477456016000251370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from functools import partial from typing import TYPE_CHECKING from .dbus_common_elements import DbusLocalObjectMeta from .dbus_proxy_async_interface_base import ( DbusExportHandle, DbusInterfaceBaseAsync, ) from .dbus_proxy_async_interfaces import DbusInterfaceCommonAsync from .dbus_proxy_async_method import dbus_method_async from .dbus_proxy_async_signal import dbus_signal_async from .default_bus import get_default_bus if TYPE_CHECKING: from collections.abc import Callable from typing import Any, Optional from .sd_bus_internals import SdBus, SdBusSlot class DbusObjectManagerExportHandle(DbusExportHandle): def __init__( self, local_meta: DbusLocalObjectMeta, remove_object_call: Callable[[], None], ): super().__init__(local_meta) self.remove_object_call = remove_object_call def stop(self) -> None: super().stop() self.remove_object_call() class DbusObjectManagerInterfaceAsync( DbusInterfaceCommonAsync, interface_name='org.freedesktop.DBus.ObjectManager', serving_enabled=False, ): def __init__(self) -> None: super().__init__() self._object_manager_slot: Optional[SdBusSlot] = None self._managed_object_to_path: dict[DbusInterfaceBaseAsync, str] = {} @dbus_method_async(result_signature='a{oa{sa{sv}}}') async def get_managed_objects( self) -> dict[str, dict[str, dict[str, Any]]]: raise NotImplementedError @dbus_signal_async('oa{sa{sv}}') def interfaces_added(self) -> tuple[str, dict[str, dict[str, Any]]]: raise NotImplementedError @dbus_signal_async('oao') def interfaces_removed(self) -> tuple[str, list[str]]: raise NotImplementedError def _dbus_on_no_members_exported(self) -> None: ... # Object manager is allowed to be exported empty def export_to_dbus( self, object_path: str, bus: Optional[SdBus] = None, ) -> DbusExportHandle: if bus is None: bus = get_default_bus() export_handle = super().export_to_dbus( object_path, bus, ) slot = bus.add_object_manager(object_path) self._object_manager_slot = slot export_handle._dbus_slots.append(slot) return export_handle def export_with_manager( self, object_path: str, object_to_export: DbusInterfaceBaseAsync, bus: Optional[SdBus] = None, ) -> DbusObjectManagerExportHandle: if self._object_manager_slot is None: raise RuntimeError('ObjectManager not intitialized') if bus is None: bus = get_default_bus() object_to_export.export_to_dbus( object_path, bus, ) meta = object_to_export._dbus if not isinstance(meta, DbusLocalObjectMeta): raise TypeError handle = DbusObjectManagerExportHandle( meta, partial(self.remove_managed_object, object_to_export), ) bus.emit_object_added(object_path) self._managed_object_to_path[object_to_export] = object_path return handle def remove_managed_object( self, managed_object: DbusInterfaceBaseAsync) -> None: if self._dbus.attached_bus is None: raise RuntimeError('Object manager not exported') removed_path = self._managed_object_to_path.pop(managed_object) self._dbus.attached_bus.emit_object_removed(removed_path) python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_property.py000066400000000000000000000234771477456016000241020ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from collections.abc import Awaitable from inspect import iscoroutinefunction from types import FunctionType from typing import TYPE_CHECKING, Generic, TypeVar, cast, overload from weakref import ref as weak_ref from .dbus_common_elements import ( DbusBoundAsync, DbusMemberAsync, DbusPropertyCommon, DbusPropertyOverride, DbusRemoteObjectMeta, ) if TYPE_CHECKING: from collections.abc import Callable, Generator from typing import Any, Optional, Union from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync from .sd_bus_internals import SdBusMessage T = TypeVar('T') class DbusPropertyAsync(DbusMemberAsync, DbusPropertyCommon, Generic[T]): def __init__( self, property_name: Optional[str], property_signature: str, property_getter: Callable[[DbusInterfaceBaseAsync], T], property_setter: Optional[ Callable[[DbusInterfaceBaseAsync, T], None]], flags: int, ) -> None: assert isinstance(property_getter, FunctionType) super().__init__( property_name, property_signature, flags, property_getter, ) self.property_getter: Callable[ [DbusInterfaceBaseAsync], T] = property_getter self.property_setter: Optional[ Callable[[DbusInterfaceBaseAsync, T], None]] = property_setter self.property_setter_is_public: bool = True self.__doc__ = property_getter.__doc__ @overload def __get__( self, obj: None, obj_class: type[DbusInterfaceBaseAsync], ) -> DbusPropertyAsync[T]: ... @overload def __get__( self, obj: DbusInterfaceBaseAsync, obj_class: type[DbusInterfaceBaseAsync], ) -> DbusBoundPropertyAsyncBase[T]: ... def __get__( self, obj: Optional[DbusInterfaceBaseAsync], obj_class: Optional[type[DbusInterfaceBaseAsync]] = None, ) -> Union[DbusBoundPropertyAsyncBase[T], DbusPropertyAsync[T]]: if obj is not None: dbus_meta = obj._dbus if isinstance(dbus_meta, DbusRemoteObjectMeta): return DbusProxyPropertyAsync(self, dbus_meta) else: return DbusLocalPropertyAsync(self, obj) else: return self def setter(self, new_set_function: Callable[ [Any, T], None], ) -> None: assert self.property_setter is None, "Setter already defined" assert not iscoroutinefunction(new_set_function), ( "Property setter can't be coroutine", ) self.property_setter = new_set_function def setter_private( self, new_set_function: Callable[ [Any, T], None], ) -> None: assert self.property_setter is None, "Setter already defined" assert not iscoroutinefunction(new_set_function), ( "Property setter can't be coroutine", ) self.property_setter = new_set_function self.property_setter_is_public = False class DbusBoundPropertyAsyncBase(DbusBoundAsync, Awaitable[T]): def __await__(self) -> Generator[Any, None, T]: return self.get_async().__await__() async def get_async(self) -> T: raise NotImplementedError async def set_async(self, complete_object: T) -> None: raise NotImplementedError class DbusProxyPropertyAsync(DbusBoundPropertyAsyncBase[T]): def __init__( self, dbus_property: DbusPropertyAsync[T], proxy_meta: DbusRemoteObjectMeta, ): self.dbus_property = dbus_property self.proxy_meta = proxy_meta self.__doc__ = dbus_property.__doc__ async def get_async(self) -> T: bus = self.proxy_meta.attached_bus new_get_message = ( bus.new_property_get_message( self.proxy_meta.service_name, self.proxy_meta.object_path, self.dbus_property.interface_name, self.dbus_property.property_name, ) ) reply_message = await bus.call_async(new_get_message) # Get method returns variant but we only need contents of variant return cast(T, reply_message.get_contents()[1]) async def set_async(self, complete_object: T) -> None: bus = self.proxy_meta.attached_bus new_set_message = ( bus.new_property_set_message( self.proxy_meta.service_name, self.proxy_meta.object_path, self.dbus_property.interface_name, self.dbus_property.property_name, ) ) new_set_message.append_data( 'v', (self.dbus_property.property_signature, complete_object), ) await bus.call_async(new_set_message) class DbusLocalPropertyAsync(DbusBoundPropertyAsyncBase[T]): def __init__( self, dbus_property: DbusPropertyAsync[T], local_object: DbusInterfaceBaseAsync, ): self.dbus_property = dbus_property self.local_object_ref = weak_ref(local_object) self.__doc__ = dbus_property.__doc__ async def get_async(self) -> T: local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") return self.dbus_property.property_getter(local_object) async def set_async(self, complete_object: T) -> None: if self.dbus_property.property_setter is None: raise RuntimeError("Property has no setter") local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") self.dbus_property.property_setter( local_object, complete_object, ) try: properties_changed = getattr( local_object, "properties_changed", ) except AttributeError: ... else: properties_changed.emit( ( self.dbus_property.interface_name, { self.dbus_property.property_name: ( self.dbus_property.property_signature, complete_object, ), }, [] ) ) def _dbus_reply_get(self, message: SdBusMessage) -> None: local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") reply_data: Any = self.dbus_property.property_getter(local_object) message.append_data(self.dbus_property.property_signature, reply_data) def _dbus_reply_set(self, message: SdBusMessage) -> None: local_object = self.local_object_ref() if local_object is None: raise RuntimeError("Local object no longer exists!") assert self.dbus_property.property_setter is not None data_to_set_to: Any = message.get_contents() self.dbus_property.property_setter(local_object, data_to_set_to) try: properties_changed = getattr( local_object, "properties_changed", ) except AttributeError: ... else: properties_changed.emit( ( self.dbus_property.interface_name, { self.dbus_property.property_name: ( self.dbus_property.property_signature, data_to_set_to, ), }, [] ) ) def dbus_property_async( property_signature: str = "", flags: int = 0, property_name: Optional[str] = None, ) -> Callable[ [Callable[[Any], T]], DbusPropertyAsync[T]]: assert not isinstance(property_signature, FunctionType), ( "Passed function to decorator directly. " "Did you forget () round brackets?" ) def property_decorator( function: Callable[..., Any] ) -> DbusPropertyAsync[T]: assert not iscoroutinefunction(function), ( "Property getter can't be coroutine", ) new_wrapper: DbusPropertyAsync[T] = DbusPropertyAsync( property_name, property_signature, function, None, flags, ) return new_wrapper return property_decorator def dbus_property_async_override() -> Callable[ [Callable[[Any], T]], DbusPropertyAsync[T]]: def new_decorator( new_property: Callable[[Any], T]) -> DbusPropertyAsync[T]: return cast(DbusPropertyAsync[T], DbusPropertyOverride(new_property)) return new_decorator python-sdbus-0.14.0/src/sdbus/dbus_proxy_async_signal.py000066400000000000000000000222101477456016000234530ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import Queue from collections.abc import AsyncIterable, AsyncIterator from contextlib import closing from types import FunctionType from typing import TYPE_CHECKING, Generic, TypeVar, cast, overload from weakref import WeakSet from .dbus_common_elements import ( DbusBoundAsync, DbusLocalObjectMeta, DbusMemberAsync, DbusRemoteObjectMeta, DbusSignalCommon, ) from .default_bus import get_default_bus if TYPE_CHECKING: from collections.abc import Callable, Sequence from typing import Any, Optional, Union from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync from .sd_bus_internals import SdBus, SdBusMessage, SdBusSlot T = TypeVar('T') class DbusSignalAsync(DbusMemberAsync, DbusSignalCommon, Generic[T]): def __init__( self, signal_name: Optional[str], signal_signature: str, args_names: Sequence[str], flags: int, original_method: FunctionType ): super().__init__( signal_name, signal_signature, args_names, flags, original_method, ) self.local_callbacks: WeakSet[Callable[[T], Any]] = WeakSet() @overload def __get__( self, obj: None, obj_class: type[DbusInterfaceBaseAsync], ) -> DbusSignalAsync[T]: ... @overload def __get__( self, obj: DbusInterfaceBaseAsync, obj_class: type[DbusInterfaceBaseAsync], ) -> DbusBoundSignalAsyncBase[T]: ... def __get__( self, obj: Optional[DbusInterfaceBaseAsync], obj_class: Optional[type[DbusInterfaceBaseAsync]] = None, ) -> Union[DbusBoundSignalAsyncBase[T], DbusSignalAsync[T]]: if obj is not None: dbus_meta = obj._dbus if isinstance(dbus_meta, DbusRemoteObjectMeta): return DbusProxySignalAsync(self, dbus_meta) else: return DbusLocalSignalAsync(self, dbus_meta) else: return self async def catch_anywhere( self, service_name: str, bus: Optional[SdBus] = None, ) -> AsyncIterable[tuple[str, T]]: if bus is None: bus = get_default_bus() message_queue: Queue[SdBusMessage] = Queue() match_slot = await bus.match_signal_async( service_name, None, self.interface_name, self.signal_name, message_queue.put_nowait, ) with closing(match_slot): while True: next_signal_message = await message_queue.get() signal_path = next_signal_message.path assert signal_path is not None yield ( signal_path, cast(T, next_signal_message.get_contents()) ) class DbusBoundSignalAsyncBase(DbusBoundAsync, AsyncIterable[T], Generic[T]): async def catch(self) -> AsyncIterator[T]: raise NotImplementedError yield cast(T, None) __aiter__ = catch async def catch_anywhere( self, service_name: Optional[str] = None, bus: Optional[SdBus] = None, ) -> AsyncIterable[tuple[str, T]]: raise NotImplementedError yield "", cast(T, None) def emit(self, args: T) -> None: raise NotImplementedError class DbusProxySignalAsync(DbusBoundSignalAsyncBase[T]): def __init__( self, dbus_signal: DbusSignalAsync[T], proxy_meta: DbusRemoteObjectMeta, ): self.dbus_signal = dbus_signal self.proxy_meta = proxy_meta self.__doc__ = dbus_signal.__doc__ async def _register_match_slot( self, bus: SdBus, callback: Callable[[SdBusMessage], Any], ) -> SdBusSlot: return await bus.match_signal_async( self.proxy_meta.service_name, self.proxy_meta.object_path, self.dbus_signal.interface_name, self.dbus_signal.signal_name, callback, ) async def catch(self) -> AsyncIterator[T]: message_queue: Queue[SdBusMessage] = Queue() match_slot = await self._register_match_slot( self.proxy_meta.attached_bus, message_queue.put_nowait, ) with closing(match_slot): while True: next_signal_message = await message_queue.get() yield cast(T, next_signal_message.get_contents()) __aiter__ = catch async def catch_anywhere( self, service_name: Optional[str] = None, bus: Optional[SdBus] = None, ) -> AsyncIterable[tuple[str, T]]: if bus is None: bus = self.proxy_meta.attached_bus if service_name is None: service_name = self.proxy_meta.service_name message_queue: Queue[SdBusMessage] = Queue() match_slot = await bus.match_signal_async( service_name, None, self.dbus_signal.interface_name, self.dbus_signal.signal_name, message_queue.put_nowait, ) with closing(match_slot): while True: next_signal_message = await message_queue.get() signal_path = next_signal_message.path assert signal_path is not None yield ( signal_path, cast(T, next_signal_message.get_contents()) ) def emit(self, args: T) -> None: raise RuntimeError("Cannot emit signal from D-Bus proxy.") class DbusLocalSignalAsync(DbusBoundSignalAsyncBase[T]): def __init__( self, dbus_signal: DbusSignalAsync[T], local_meta: DbusLocalObjectMeta, ): self.dbus_signal = dbus_signal self.local_meta = local_meta self.__doc__ = dbus_signal.__doc__ async def catch(self) -> AsyncIterator[T]: new_queue: Queue[T] = Queue() signal_callbacks = self.dbus_signal.local_callbacks try: put_method = new_queue.put_nowait signal_callbacks.add(put_method) while True: next_data = await new_queue.get() yield next_data finally: signal_callbacks.remove(put_method) __aiter__ = catch async def catch_anywhere( self, service_name: Optional[str] = None, bus: Optional[SdBus] = None, ) -> AsyncIterable[tuple[str, T]]: raise NotImplementedError("TODO") yield def _emit_dbus_signal(self, args: T) -> None: attached_bus = self.local_meta.attached_bus if attached_bus is None: return serving_object_path = self.local_meta.serving_object_path if serving_object_path is None: return signal_message = attached_bus.new_signal_message( serving_object_path, self.dbus_signal.interface_name, self.dbus_signal.signal_name, ) if ((not self.dbus_signal.signal_signature.startswith('(')) and isinstance(args, tuple)): signal_message.append_data( self.dbus_signal.signal_signature, *args) elif self.dbus_signal.signal_signature == '' and args is None: ... else: signal_message.append_data( self.dbus_signal.signal_signature, args) signal_message.send() def emit(self, args: T) -> None: self._emit_dbus_signal(args) for callback in self.dbus_signal.local_callbacks: callback(args) def dbus_signal_async( signal_signature: str = '', signal_args_names: Sequence[str] = (), flags: int = 0, signal_name: Optional[str] = None, ) -> Callable[ [Callable[[Any], T]], DbusSignalAsync[T] ]: assert not isinstance(signal_signature, FunctionType), ( "Passed function to decorator directly. " "Did you forget () round brackets?" ) def signal_decorator( pseudo_function: Callable[[Any], T]) -> DbusSignalAsync[T]: assert isinstance(pseudo_function, FunctionType) return DbusSignalAsync( signal_name, signal_signature, signal_args_names, flags, pseudo_function, ) return signal_decorator python-sdbus-0.14.0/src/sdbus/dbus_proxy_sync_interface_base.py000066400000000000000000000132241477456016000247740ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from itertools import chain from typing import TYPE_CHECKING from weakref import WeakKeyDictionary, WeakValueDictionary from .dbus_common_elements import ( DbusClassMeta, DbusInterfaceMetaCommon, DbusMemberAsync, DbusMemberCommon, DbusRemoteObjectMeta, ) from .dbus_proxy_sync_method import DbusMethodSync from .dbus_proxy_sync_property import DbusPropertySync if TYPE_CHECKING: from collections.abc import Iterable, Iterator from typing import Any, Optional from .sd_bus_internals import SdBus DBUS_CLASS_TO_META: WeakKeyDictionary[ type, DbusClassMeta] = WeakKeyDictionary() DBUS_INTERFACE_NAME_TO_CLASS: WeakValueDictionary[ str, DbusInterfaceMetaSync] = WeakValueDictionary() class DbusInterfaceMetaSync(DbusInterfaceMetaCommon): @staticmethod def _check_collisions( new_class_name: str, attr_names: set[str], reserved_attr_names: set[str], ) -> None: possible_collisions = attr_names & reserved_attr_names if possible_collisions: raise ValueError( f"Interface {new_class_name!r} redefines reserved " f"D-Bus attribute names: {possible_collisions!r}" ) @staticmethod def _collect_dbus_to_python_attr_names( new_class_name: str, base_classes: Iterable[type], ) -> set[str]: all_python_dbus_attrs: set[str] = set() possible_collisions: set[str] = set() for c in base_classes: dbus_meta = DBUS_CLASS_TO_META.get(c) if dbus_meta is None: continue base_python_dbus_attrs = set( dbus_meta.python_attr_to_dbus_member.keys() ) possible_collisions.update( base_python_dbus_attrs & all_python_dbus_attrs ) all_python_dbus_attrs.update( base_python_dbus_attrs ) if possible_collisions: raise ValueError( f"Interface {new_class_name!r} has a reserved D-Bus " f"attribute name collision: {possible_collisions!r}" ) return all_python_dbus_attrs @staticmethod def _map_dbus_elements( attr_name: str, attr: Any, meta: DbusClassMeta, ) -> None: if not isinstance(attr, DbusMemberCommon): return if isinstance(attr, DbusMemberAsync): raise TypeError( f"Can't mix async methods in sync interface: {attr_name!r}" ) if isinstance(attr, DbusMethodSync): meta.dbus_member_to_python_attr[attr.method_name] = attr_name meta.python_attr_to_dbus_member[attr_name] = attr.method_name elif isinstance(attr, DbusPropertySync): meta.dbus_member_to_python_attr[attr.property_name] = attr_name meta.python_attr_to_dbus_member[attr_name] = attr.property_name else: raise TypeError(f"Unknown D-Bus element: {attr!r}") def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], interface_name: Optional[str] = None, serving_enabled: bool = True, ) -> DbusInterfaceMetaSync: if interface_name in DBUS_INTERFACE_NAME_TO_CLASS: raise ValueError( f"D-Bus interface of the name {interface_name!r} was " "already created." ) all_mro_bases: set[type[Any]] = set( chain.from_iterable(c.__mro__ for c in bases) ) reserved_attr_names = cls._collect_dbus_to_python_attr_names( name, all_mro_bases, ) cls._check_collisions(name, set(namespace.keys()), reserved_attr_names) new_cls = super().__new__( cls, name, bases, namespace, interface_name, serving_enabled, ) if interface_name is not None: dbus_class_meta = DbusClassMeta(interface_name, serving_enabled) DBUS_CLASS_TO_META[new_cls] = dbus_class_meta DBUS_INTERFACE_NAME_TO_CLASS[interface_name] = new_cls for attr_name, attr in namespace.items(): cls._map_dbus_elements(attr_name, attr, dbus_class_meta) return new_cls class DbusInterfaceBase(metaclass=DbusInterfaceMetaSync): def __init__( self, service_name: str, object_path: str, bus: Optional[SdBus] = None, ): self._dbus = DbusRemoteObjectMeta(service_name, object_path, bus) @classmethod def _dbus_iter_interfaces_meta( cls, ) -> Iterator[tuple[str, DbusClassMeta]]: for base in cls.__mro__: meta = DBUS_CLASS_TO_META.get(base) if meta is None: continue yield meta.interface_name, meta python-sdbus-0.14.0/src/sdbus/dbus_proxy_sync_interfaces.py000066400000000000000000000066501477456016000241720ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from .dbus_proxy_sync_interface_base import DbusInterfaceBase from .dbus_proxy_sync_method import dbus_method if TYPE_CHECKING: from typing import Any, Literal class DbusPeerInterface( DbusInterfaceBase, interface_name='org.freedesktop.DBus.Peer', serving_enabled=False, ): @dbus_method(method_name='Ping') def dbus_ping(self) -> None: raise NotImplementedError @dbus_method(method_name='GetMachineId') def dbus_machine_id(self) -> str: raise NotImplementedError class DbusIntrospectable( DbusInterfaceBase, interface_name='org.freedesktop.DBus.Introspectable', serving_enabled=False, ): @dbus_method(method_name='Introspect') def dbus_introspect(self) -> str: raise NotImplementedError class DbusPropertiesInterface( DbusInterfaceBase, interface_name='org.freedesktop.DBus.Properties', serving_enabled=False, ): @dbus_method('s', 'a{sv}', method_name='GetAll') def _properties_get_all( self, interface_name: str) -> dict[str, tuple[str, Any]]: raise NotImplementedError def properties_get_all_dict( self, on_unknown_member: Literal['error', 'ignore', 'reuse'] = 'error', ) -> dict[str, Any]: properties: dict[str, Any] = {} for interface_name, meta in self._dbus_iter_interfaces_meta(): if not meta.serving_enabled: continue dbus_properties_data = self._properties_get_all(interface_name) for member_name, variant in dbus_properties_data.items(): try: python_name = meta.dbus_member_to_python_attr[member_name] except KeyError: if on_unknown_member == 'error': raise elif on_unknown_member == 'ignore': continue elif on_unknown_member == 'reuse': python_name = member_name else: raise ValueError properties[python_name] = variant[1] return properties class DbusInterfaceCommon( DbusPeerInterface, DbusIntrospectable, DbusPropertiesInterface): ... class DbusObjectManagerInterface( DbusInterfaceCommon, interface_name='org.freedesktop.DBus.ObjectManager', serving_enabled=False, ): @dbus_method(result_signature='a{oa{sa{sv}}}') def get_managed_objects( self) -> dict[str, dict[str, dict[str, Any]]]: raise NotImplementedError python-sdbus-0.14.0/src/sdbus/dbus_proxy_sync_method.py000066400000000000000000000076271477456016000233340ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from inspect import iscoroutinefunction from types import FunctionType from typing import TYPE_CHECKING, TypeVar, cast from .dbus_common_elements import ( DbusBoundSync, DbusMemberSync, DbusMethodCommon, ) if TYPE_CHECKING: from collections.abc import Callable, Sequence from typing import Any, Optional from .dbus_proxy_sync_interface_base import DbusInterfaceBase T = TypeVar('T') class DbusMethodSync(DbusMethodCommon, DbusMemberSync): def __get__(self, obj: DbusInterfaceBase, obj_class: Optional[type[DbusInterfaceBase]] = None, ) -> Callable[..., Any]: return DbusLocalMethodSync(self, obj) class DbusLocalMethodSync(DbusBoundSync): def __init__(self, dbus_method: DbusMethodSync, interface: DbusInterfaceBase): self.dbus_method = dbus_method self.interface = interface self.__doc__ = dbus_method.__doc__ def _call_dbus_sync(self, *args: Any) -> Any: new_call_message = ( self.interface._dbus.attached_bus.new_method_call_message( self.interface._dbus.service_name, self.interface._dbus.object_path, self.dbus_method.interface_name, self.dbus_method.method_name, ) ) if args: new_call_message.append_data( self.dbus_method.input_signature, *args) reply_message = self.interface._dbus.attached_bus.call( new_call_message) return reply_message.get_contents() def __call__(self, *args: Any, **kwargs: Any) -> Any: if len(args) == self.dbus_method.num_of_args: assert not kwargs, ( "Passed more arguments than method supports" f"Extra args: {kwargs}") rebuilt_args: Sequence[Any] = args else: rebuilt_args = self.dbus_method._rebuild_args( self.dbus_method.original_method, *args, **kwargs) return self._call_dbus_sync(*rebuilt_args) def dbus_method( input_signature: str = "", result_signature: str = "", flags: int = 0, method_name: Optional[str] = None, ) -> Callable[[T], T]: assert not isinstance(input_signature, FunctionType), ( "Passed function to decorator directly. " "Did you forget () round brackets?" ) def dbus_method_decorator(original_method: T) -> T: assert isinstance(original_method, FunctionType) assert not iscoroutinefunction(original_method), ( "Expected NON coroutine function. ", "Maybe you wanted to remove 'async' keyword?", ) new_wrapper = DbusMethodSync( original_method=original_method, method_name=method_name, input_signature=input_signature, result_signature=result_signature, result_args_names=(), input_args_names=(), flags=flags, ) return cast(T, new_wrapper) return dbus_method_decorator python-sdbus-0.14.0/src/sdbus/dbus_proxy_sync_property.py000066400000000000000000000106041477456016000237250ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from inspect import iscoroutinefunction from types import FunctionType from typing import TYPE_CHECKING, Generic, TypeVar, cast from .dbus_common_elements import DbusMemberSync, DbusPropertyCommon from .dbus_common_funcs import _check_sync_in_async_env if TYPE_CHECKING: from collections.abc import Callable from typing import Any, Optional from .dbus_proxy_sync_interface_base import DbusInterfaceBase T = TypeVar('T') class DbusPropertySync(DbusPropertyCommon, DbusMemberSync, Generic[T]): def __init__( self, property_name: Optional[str], property_signature: str, property_getter: Callable[[DbusInterfaceBase], T], property_setter: Optional[ Callable[[DbusInterfaceBase, T], None]], flags: int, ) -> None: assert isinstance(property_getter, FunctionType) super().__init__( property_name, property_signature, flags, property_getter, ) self.property_getter = property_getter self.property_setter = property_setter self.__doc__ = property_getter.__doc__ def __get__(self, obj: DbusInterfaceBase, obj_class: Optional[type[DbusInterfaceBase]] = None, ) -> T: assert _check_sync_in_async_env(), ( "Used sync __get__ method in async environment. " "This is probably an error as it will block " "other asyncio methods for considerable time." ) new_call_message = ( obj._dbus.attached_bus.new_property_get_message( obj._dbus.service_name, obj._dbus.object_path, self.interface_name, self.property_name, ) ) reply_message = obj._dbus.attached_bus.call(new_call_message) return cast(T, reply_message.get_contents()[1]) def __set__(self, obj: DbusInterfaceBase, value: T) -> None: assert _check_sync_in_async_env(), ( "Used sync __set__ method in async environment. " "This is probably an error as it will block " "other asyncio methods for considerable time." ) if not self.property_signature: raise AttributeError('D-Bus property is read only') new_call_message = ( obj._dbus.attached_bus.new_property_set_message( obj._dbus.service_name, obj._dbus.object_path, self.interface_name, self.property_name, ) ) new_call_message.append_data( 'v', (self.property_signature, value)) obj._dbus.attached_bus.call(new_call_message) def dbus_property( property_signature: str = "", flags: int = 0, property_name: Optional[str] = None, ) -> Callable[ [Callable[[Any], T]], DbusPropertySync[T]]: assert not isinstance(property_signature, FunctionType), ( "Passed function to decorator directly. " "Did you forget () round brackets?" ) def property_decorator( function: Callable[..., Any] ) -> DbusPropertySync[T]: assert not iscoroutinefunction(function), "Expected regular function" new_wrapper: DbusPropertySync[T] = DbusPropertySync( property_name, property_signature, function, None, flags, ) return new_wrapper return property_decorator python-sdbus-0.14.0/src/sdbus/default_bus.py000066400000000000000000000135231477456016000210270ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations import threading from contextvars import ContextVar, Token from logging import getLogger from typing import TYPE_CHECKING from .sd_bus_internals import ( NameAllowReplacementFlag, NameQueueFlag, NameReplaceExistingFlag, sd_bus_open, ) if TYPE_CHECKING: from typing import Optional from .sd_bus_internals import SdBus logger = getLogger(__name__) class DefaultBusTLStorage(threading.local): bus: Optional[SdBus] = None bus_tls = DefaultBusTLStorage() bus_contextvar: ContextVar[SdBus] = ContextVar("DEFAULT_BUS") def _get_defaul_bus_tls() -> Optional[SdBus]: return bus_tls.bus def _set_default_bus_tls(new_bus: Optional[SdBus]) -> None: bus_tls.bus = new_bus def get_default_bus() -> SdBus: """Get default bus. Returns context-local default bus if set or thread-local otherwise. If no default bus is set initializes a new bus using :py:func:`sdbus.sd_bus_open` and sets it as a thread-local default bus. """ if (context_bus := bus_contextvar.get(None)) is not None: return context_bus if (tls_bus := _get_defaul_bus_tls()) is not None: return tls_bus else: new_bus = sd_bus_open() logger.info( "Created new default bus for thread %r", threading.current_thread(), ) _set_default_bus_tls(new_bus) return new_bus def set_default_bus(new_default: SdBus) -> None: """Set thread-local default bus. Should be called before creating any objects that will use default bus. Default bus can be replaced but the change will only affect newly created objects. """ _set_default_bus_tls(new_default) def set_context_default_bus(new_default: SdBus) -> Token[SdBus]: """Set context-local default bus. Should be called before creating any objects that will use default bus. Default bus can be replaced but the change will only affect newly created objects. Context-local default bus has higher priority over thread-local one but has to be explicitly set. :returns: Token that can be used to reset context bus back. See ``contextvars`` documentation for details. """ return bus_contextvar.set(new_default) def _prepare_request_name_flags( allow_replacement: bool, replace_existing: bool, queue: bool, ) -> int: return ( (NameAllowReplacementFlag if allow_replacement else 0) + (NameReplaceExistingFlag if replace_existing else 0) + (NameQueueFlag if queue else 0) ) async def request_default_bus_name_async( new_name: str, allow_replacement: bool = False, replace_existing: bool = False, queue: bool = False, ) -> None: r"""Asynchronously acquire a name on the default bus. :param new_name: Name to acquire. Must be a valid D-Bus service name. :param allow_replacement: If name was acquired allow other D-Bus peers to take away the name. :param replace_existing: If current name owner allows, take away the name. :param queue: Queue up for name acquisition. :py:exc:`.SdBusRequestNameInQueueError` will be raised when successfully placed in queue. :py:meth:`Ownership change signal ` should be monitored get notified when the name was acquired. :raises: :ref:`name-request-exceptions` and other D-Bus exceptions. """ default_bus = get_default_bus() await default_bus.request_name_async( new_name, _prepare_request_name_flags( allow_replacement, replace_existing, queue, ) ) def request_default_bus_name( new_name: str, allow_replacement: bool = False, replace_existing: bool = False, queue: bool = False, ) -> None: r"""Acquire a name on the default bus. Blocks until a reply is received from D-Bus daemon. :param new_name: Name to acquire. Must be a valid D-Bus service name. :param allow_replacement: If name was acquired allow other D-Bus peers to take away the name. :param replace_existing: If current name owner allows, take away the name. :param queue: Queue up for name acquisition. :py:exc:`.SdBusRequestNameInQueueError` will be raised when successfully placed in queue. :py:meth:`Ownership change signal ` should be monitored get notified when the name was acquired. :raises: :ref:`name-request-exceptions` and other D-Bus exceptions. """ default_bus = get_default_bus() default_bus.request_name( new_name, _prepare_request_name_flags( allow_replacement, replace_existing, queue, ) ) __all__ = ( "get_default_bus", "set_default_bus", "set_context_default_bus", "request_default_bus_name_async", "request_default_bus_name", ) python-sdbus-0.14.0/src/sdbus/exceptions.py000066400000000000000000000062461477456016000207170ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from .dbus_exceptions import ( DbusAccessDeniedError, DbusAddressInUseError, DbusAuthFailedError, DbusBadAddressError, DbusDisconnectedError, DbusFailedError, DbusFileExistsError, DbusFileNotFoundError, DbusInconsistentMessageError, DbusInteractiveAuthorizationRequiredError, DbusInvalidArgsError, DbusInvalidFileContentError, DbusInvalidSignatureError, DbusIOError, DbusLimitsExceededError, DbusMatchRuleInvalidError, DbusMatchRuleNotFound, DbusNameHasNoOwnerError, DbusNoMemoryError, DbusNoNetworkError, DbusNoReplyError, DbusNoServerError, DbusNotSupportedError, DbusPropertyReadOnlyError, DbusServiceUnknownError, DbusTimeoutError, DbusUnixProcessIdUnknownError, DbusUnknownInterfaceError, DbusUnknownMethodError, DbusUnknownObjectError, DbusUnknownPropertyError, ) from .sd_bus_internals import ( SdBusBaseError, SdBusLibraryError, SdBusRequestNameAlreadyOwnerError, SdBusRequestNameError, SdBusRequestNameExistsError, SdBusRequestNameInQueueError, SdBusUnmappedMessageError, map_exception_to_dbus_error, ) __all__ = ( 'DbusAccessDeniedError', 'DbusAddressInUseError', 'DbusAuthFailedError', 'DbusBadAddressError', 'DbusDisconnectedError', 'DbusFailedError', 'DbusFileExistsError', 'DbusFileNotFoundError', 'DbusInconsistentMessageError', 'DbusInteractiveAuthorizationRequiredError', 'DbusInvalidArgsError', 'DbusInvalidFileContentError', 'DbusInvalidSignatureError', 'DbusIOError', 'DbusLimitsExceededError', 'DbusMatchRuleInvalidError', 'DbusMatchRuleNotFound', 'DbusNameHasNoOwnerError', 'DbusNoMemoryError', 'DbusNoNetworkError', 'DbusNoReplyError', 'DbusNoServerError', 'DbusNotSupportedError', 'DbusPropertyReadOnlyError', 'DbusServiceUnknownError', 'DbusTimeoutError', 'DbusUnixProcessIdUnknownError', 'DbusUnknownInterfaceError', 'DbusUnknownMethodError', 'DbusUnknownObjectError', 'DbusUnknownPropertyError', 'map_exception_to_dbus_error', 'SdBusBaseError', 'SdBusLibraryError', 'SdBusRequestNameAlreadyOwnerError', 'SdBusRequestNameError', 'SdBusRequestNameExistsError', 'SdBusRequestNameInQueueError', 'SdBusUnmappedMessageError', ) python-sdbus-0.14.0/src/sdbus/interface_generator.py000066400000000000000000000605451477456016000225460ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING from xml.etree.ElementTree import fromstring as etree_from_str from xml.etree.ElementTree import parse as etree_from_file from .dbus_common_funcs import snake_case_to_camel_case if TYPE_CHECKING: from collections.abc import Iterable, Iterator from typing import Literal, Optional, Union from xml.etree.ElementTree import Element def _camel_case_to_snake_case_generator(camel: str) -> Iterator[str]: i = iter(camel) # First character is directly converted to low try: first_char = next(i) except StopIteration: raise ValueError('Name too short') yield first_char.lower() last_character = first_char # Yield every character # if upper is encountered # yield _ only if previous character # was not already uppercase or underscore while True: try: c = next(i) except StopIteration: return if c.isupper(): if not last_character.isupper() and not last_character == "_": yield '_' yield c.lower() else: yield c last_character = c def camel_case_to_snake_case(camel: str) -> str: return ''.join(_camel_case_to_snake_case_generator(camel)) def _iterface_name_to_class_generator(interface_name: str) -> Iterator[str]: i = iter(interface_name) try: first_char = next(i) except StopIteration: raise ValueError('Interface name too short') yield first_char.upper() up_next_char = False for _ in range(120): try: c = next(i) except StopIteration: return if c == '.': up_next_char = True else: if up_next_char: yield c.upper() else: yield c up_next_char = False def interface_name_to_class(interface_name: str) -> str: return ''.join(_iterface_name_to_class_generator(interface_name)) def parse_str_bool(annotation_value: str) -> bool: if annotation_value == 'true': return True elif annotation_value == 'false': return False else: raise ValueError(f"Unknown bool value: {annotation_value}") class DbusSigToTyping: _DBUS_BASIC_SIG_TO_TYPING = { 'y': 'int', 'b': 'bool', 'n': 'int', 'q': 'int', 'i': 'int', 'u': 'int', 'x': 'int', 't': 'int', 'd': 'float', 's': 'str', 'o': 'str', 'g': 'str', 'h': 'int', } @classmethod def typing_basic(cls, char: str) -> str: return cls._DBUS_BASIC_SIG_TO_TYPING[char] @staticmethod def typing_into_tuple(typing_iter: Iterable[str]) -> str: return f"tuple[{', '.join(typing_iter)}]" @staticmethod def slice_container(dbus_sig_iter: Iterator[str], peek_str: str) -> str: accumulator: list[str] = [peek_str] round_braces_count = 0 curly_braces_count = 0 if peek_str == '(': round_braces_count += 1 if peek_str == '{': curly_braces_count += 1 while True: try: char = next(dbus_sig_iter) except StopIteration: break if char == ')': round_braces_count -= 1 if char == '}': curly_braces_count -= 1 if char == '(': round_braces_count += 1 if char == '{': curly_braces_count += 1 accumulator.append(char) if char == 'a': continue if round_braces_count == 0 and curly_braces_count == 0: break return ''.join(accumulator) @classmethod def split_sig(cls, sig: str) -> list[str]: completes: list[str] = [] sig_iter = iter(sig) while True: try: next_char = next(sig_iter) except StopIteration: break if next_char == '(': next_complete = cls.slice_container(sig_iter, '(') elif next_char == 'a': next_complete = cls.slice_container(sig_iter, 'a') else: next_complete = next_char completes.append(next_complete) return completes @classmethod def typing_complete(cls, complete_sig: str) -> str: if complete_sig == 'v': return cls.typing_into_tuple(('str', 'Any')) elif complete_sig == 'ay': return 'bytes' elif complete_sig.startswith('a{'): if complete_sig[-1] != '}': raise ValueError(f"Malformed dict {complete_sig}") dict_key_sig = complete_sig[2] dict_key_typing = cls.typing_basic(dict_key_sig) dict_value_sig = complete_sig[3:-1] dict_value_typing = cls.typing_complete(dict_value_sig) return f"dict[{dict_key_typing}, {dict_value_typing}]" elif complete_sig.startswith('a'): array_completes = cls.split_sig(complete_sig[1:]) if len(array_completes) != 1: raise ValueError("Array does not have only " "one complete type: {array_completes}") array_single_complete = array_completes[0] return f"list[{cls.typing_complete(array_single_complete)}]" elif complete_sig.startswith('('): if complete_sig[-1] != ')': raise ValueError(f"Malformed struct {complete_sig}") struct_completes = cls.split_sig(complete_sig[1:-1]) struct_typing = (cls.typing_complete(x) for x in struct_completes) return cls.typing_into_tuple(struct_typing) else: return cls.typing_basic(complete_sig) @classmethod def result_typing(cls, result_args: list[str]) -> str: result_len = len(result_args) if result_len == 0: return 'None' elif result_len == 1: return cls.typing_complete(result_args[0]) else: return cls.typing_into_tuple( cls.typing_complete(x) for x in result_args ) @classmethod def sig_to_typing(cls, signature: str) -> str: return cls.result_typing(cls.split_sig(signature)) class DbusMemberAbstract: def __init__(self, element: Element): self.method_name = element.attrib['name'] self.python_name = camel_case_to_snake_case(self.method_name) self.is_deprecated = False self.is_priveledged = False self.iter_sub_elements(element) def _can_use_unpivileged(self) -> bool: return True def _flags_iter(self) -> Iterator[str]: if self.is_deprecated: yield 'DbusDeprecatedFlag' if not self.is_priveledged and self._can_use_unpivileged(): yield 'DbusUnprivilegedFlag' @property def flags_str(self) -> str: return ' | '.join(self._flags_iter()) def _parse_arg(self, arg: Element) -> None: raise NotImplementedError('Member does not have arguments') def _parse_annotation_data(self, annotation_name: str, annotation_value: str) -> None: if annotation_name == 'org.freedesktop.DBus.Deprecated': self.is_deprecated = parse_str_bool(annotation_value) elif annotation_name == 'org.freedesktop.systemd1.Privileged': self.is_priveledged = parse_str_bool(annotation_value) else: ... def _parse_annotation(self, annotation: Element) -> None: if annotation.tag != 'annotation': raise ValueError('Uknown element of member: ', annotation.tag) annotation_name = annotation.attrib['name'] annotation_value = annotation.attrib['value'] self._parse_annotation_data(annotation_name, annotation_value) def iter_sub_elements(self, element: Element) -> None: for sub_element in element: tag = sub_element.tag if tag == 'annotation': self._parse_annotation(sub_element) elif tag == 'arg': self._parse_arg(sub_element) else: raise ValueError( 'Uknown member annotation tag: ', tag) @property def wants_rename(self) -> bool: return self.method_name != snake_case_to_camel_case(self.python_name) class DbusArgsIntrospection: def __init__(self, element: Element): if element.tag != 'arg': raise ValueError(f"Expected arg tag, got {element.tag}") try: self.name: Optional[str] = element.attrib['name'] except KeyError: self.name = None self.dbus_type = element.attrib['type'] direction = element.attrib.get('direction') if direction == 'in': self.is_input: Optional[bool] = True elif direction == 'out': self.is_input = False elif direction is None: self.is_input = None else: raise ValueError(f'Unknown arg direction {direction}') @property def typing(self) -> str: return DbusSigToTyping.typing_complete(self.dbus_type) def __repr__(self) -> str: return (f"D-Bus Arg: {self.name}, " f"type: {self.dbus_type}, " f"is input: {self.is_input}") class DbusMethodInrospection(DbusMemberAbstract): def __init__(self, element: Element): if element.tag != 'method': raise ValueError(f"Expected method tag, got {element.tag}") self.is_no_reply = False self.input_args: list[DbusArgsIntrospection] = [] self.result_args: list[DbusArgsIntrospection] = [] super().__init__(element) def _flags_iter(self) -> Iterator[str]: if self.is_no_reply: yield 'DbusNoReplyFlag' yield from super()._flags_iter() def _parse_arg(self, arg: Element) -> None: new_arg = DbusArgsIntrospection(arg) if new_arg.is_input or new_arg.is_input is None: self.input_args.append(new_arg) elif not new_arg.is_input: self.result_args.append(new_arg) else: raise ValueError('Malformed arg direction') @property def dbus_input_signature(self) -> str: return ''.join( x.dbus_type for x in self.input_args ) @property def dbus_result_signature(self) -> str: return ''.join( x.dbus_type if not x.is_input else '' for x in self.result_args ) @property def args_names_and_typing(self) -> list[tuple[str, str]]: arg_names: list[tuple[str, str]] = [] for i, input_arg in enumerate(self.input_args): if input_arg.name is not None: input_arg_name = camel_case_to_snake_case(input_arg.name) else: input_arg_name = f"arg_{i}" arg_names.append((input_arg_name, input_arg.typing)) return arg_names @property def result_typing(self) -> str: return DbusSigToTyping.result_typing( [x.dbus_type for x in self.result_args]) @property def is_results_args_valid_names(self) -> bool: return all(r.name is not None for r in self.result_args) @property def result_args_names_repr(self) -> str: return repr(tuple(r.name for r in self.result_args)) def __repr__(self) -> str: return (f"D-Bus Method: {self.method_name}, " f"args: {self.args_names_and_typing}, " f"result: {self.dbus_result_signature}") class DbusPropertyIntrospection(DbusMemberAbstract): _EMITS_CHANGED_MAP: dict[ Union[bool, Literal['const', 'invalidates']], str ] = { True: 'DbusPropertyEmitsChangeFlag', 'invalidates': 'DbusPropertyEmitsInvalidationFlag', 'const': 'DbusPropertyConstFlag', } def __init__(self, element: Element): if element.tag != 'property': raise ValueError(f"Expected property tag, got {element.tag}") self.dbus_signature = element.attrib['type'] self.emits_changed: Union[bool, Literal['const', 'invalidates']] = True self.is_explicit = False access_type = element.attrib['access'] if access_type == 'readwrite' or access_type == 'write': self.is_read_only = False elif access_type == 'read': self.is_read_only = True else: raise ValueError(f"Unknown property access {access_type}") super().__init__(element) def _can_use_unpivileged(self) -> bool: # Only properties that have setters defined can use the # unprivileged flags. The code generator does NOT generate # setters. return False def _flags_iter(self) -> Iterator[str]: emits_changed_str = self._EMITS_CHANGED_MAP.get(self.emits_changed) if emits_changed_str is not None: yield emits_changed_str yield from super()._flags_iter() def _parse_annotation_data(self, annotation_name: str, annotation_value: str) -> None: if annotation_name == ('org.freedesktop.DBus.Property' '.EmitsChangedSignal'): if annotation_value == 'true': self.emits_changed = True elif annotation_value == 'false': self.emits_changed = False elif annotation_value == 'const': self.emits_changed = 'const' elif annotation_value == 'invalidates': self.emits_changed = 'invalidates' else: raise ValueError('Unknown EmitsChanged value', annotation_value) elif annotation_name == 'org.freedesktop.systemd1.Explicit': self.is_explicit = parse_str_bool(annotation_value) super()._parse_annotation_data(annotation_name, annotation_value) @property def typing(self) -> str: return DbusSigToTyping.typing_complete(self.dbus_signature) class DbusSignalIntrospection(DbusMemberAbstract): def __init__(self, element: Element): if element.tag != 'signal': raise ValueError(f"Expected signal tag, got {element.tag}") self.args: list[DbusArgsIntrospection] = [] super().__init__(element) def _can_use_unpivileged(self) -> bool: return False def _parse_arg(self, arg: Element) -> None: new_arg = DbusArgsIntrospection(arg) if new_arg.is_input: raise ValueError('Signal argument cannot be in', new_arg) self.args.append(new_arg) @property def dbus_signature(self) -> str: return ''.join(x.dbus_type for x in self.args) @property def typing(self) -> str: return DbusSigToTyping.result_typing( [x.dbus_type for x in self.args]) @property def is_args_valid_names(self) -> bool: return all(a.name is not None for a in self.args) @property def args_names_repr(self) -> str: return repr(tuple(a.name for a in self.args)) class DbusInterfaceIntrospection: def __init__(self, element: Element): if element.tag != 'interface': raise ValueError(f"Expected interface tag, got {element.tag}") self.interface_name = element.attrib['name'] self.python_name = interface_name_to_class( self.interface_name) + 'Interface' self.is_deprecated = False self.c_name: Optional[str] = None self.methods: list[DbusMethodInrospection] = [] self.properties: list[DbusPropertyIntrospection] = [] self.signals: list[DbusSignalIntrospection] = [] for dbus_member in element: if dbus_member.tag == 'method': self.methods.append(DbusMethodInrospection(dbus_member)) elif dbus_member.tag == 'property': self.properties.append(DbusPropertyIntrospection(dbus_member)) elif dbus_member.tag == 'signal': self.signals.append(DbusSignalIntrospection(dbus_member)) elif dbus_member.tag == 'annotation': annotation_name = dbus_member.attrib['name'] annotation_value = dbus_member.attrib['value'] if annotation_name == 'org.freedesktop.DBus.Deprecated': self.is_deprecated = parse_str_bool(annotation_value) elif annotation_name == 'org.freedesktop.DBus.GLib.CSymbol': self.c_name = annotation_value else: ... else: raise ValueError(f'Unknown D-Bus member {dbus_member}') @property def has_members(self) -> bool: return any((self.methods, self.properties, self.signals)) SKIP_INTERFACES = { 'org.freedesktop.DBus.Properties', 'org.freedesktop.DBus.Introspectable', 'org.freedesktop.DBus.Peer', 'org.freedesktop.DBus.ObjectManager', } INTERFACE_TEMPLATES: dict[str, str] = { "generic_no_members": """\ ... # Interface has no members """, "generic_method_flags": ( """\ {% if method.dbus_input_signature %} input_signature="{{ method.dbus_input_signature }}", {% endif %} {% if method.dbus_result_signature %} result_signature="{{ method.dbus_result_signature }}", {% endif %} {% if method.flags_str %} flags={{ method.flags_str }}, {% endif %} {% if method.wants_rename %} method_name="{{method.method_name}}", {% endif %} """ ), "generic_property_flags": ( """\ {% if a_property.dbus_signature %} property_signature="{{ a_property.dbus_signature }}", {% endif %} {% if a_property.flags_str %} flags={{ a_property.flags_str }}, {% endif %} {% if a_property.wants_rename %} property_name="{{a_property.method_name}}", {% endif %} """ ), "generic_header": """\ from __future__ import annotations from typing import Any """, "async_imports_header": """from sdbus import ( DbusDeprecatedFlag, DbusInterfaceCommonAsync, DbusNoReplyFlag, DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, DbusPropertyEmitsInvalidationFlag, DbusPropertyExplicitFlag, DbusUnprivilegedFlag, dbus_method_async, dbus_property_async, dbus_signal_async, ) """, "async_main": ( """\ {% if include_import_header %} {% include 'generic_header' %} {% include 'async_imports_header' %} {% endif %} {% for interface in interfaces %} {% include 'async_interface' %} {% endfor %} """ ), "async_interface": ( """\ class {{ interface.python_name }}( DbusInterfaceCommonAsync, interface_name="{{ interface.interface_name }}", ): {% filter indent(first=True) %} {% if interface.has_members %} {% for method in interface.methods %} {% include 'async_method' %} {% endfor %} {% for a_property in interface.properties %} {% include 'async_property' %} {% endfor %} {% for signal in interface.signals %} {% include 'async_signal' %} {% endfor %} {% else %} {% include 'generic_no_members' %} {% endif %} {% endfilter %} """ ), "async_method": ( """\ @dbus_method_async( {% filter indent(first=True) %} {% include 'generic_method_flags' %} {% endfilter %} {% if method.is_results_args_valid_names %} result_args_names={{method.result_args_names_repr}}, {% endif %} ) async def {{ method.python_name }}( self, {% for arg_name, arg_type in method.args_names_and_typing %} {{ arg_name }}: {{ arg_type }}, {% endfor %} ) -> {{ method.result_typing }}: raise NotImplementedError """ ), "async_property": ( """\ @dbus_property_async( {% filter indent(first=True) %} {% include 'generic_property_flags' %} {% endfilter %} ) def {{ a_property.python_name }}(self) -> {{ a_property.typing }}: raise NotImplementedError """ ), "async_signal": ( """\ @dbus_signal_async( {% if signal.dbus_signature %} signal_signature="{{ signal.dbus_signature }}", {% endif %} {% if signal.is_args_valid_names %} signal_args_names={{signal.args_names_repr}}, {% endif %} {% if signal.flags_str %} flags={{ signal.flags_str }}, {% endif %} {% if signal.wants_rename %} signal_name=signal.method_name, {% endif %} ) def {{ signal.python_name }}(self) -> {{ signal.typing }}: raise NotImplementedError """ ), "blocking_imports_header": """\ from sdbus import ( DbusDeprecatedFlag, DbusInterfaceCommon, DbusNoReplyFlag, DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, DbusPropertyEmitsInvalidationFlag, DbusPropertyExplicitFlag, DbusUnprivilegedFlag, dbus_method, dbus_property, ) """, "blocking_main": ( """\ {% if include_import_header %} {% include 'generic_header' %} {% include 'blocking_imports_header' %} {% endif %} {% for interface in interfaces %} {% include 'blocking_interface' %} {% endfor %} """ ), "blocking_interface": ( """\ class {{ interface.python_name }}( DbusInterfaceCommon, interface_name="{{ interface.interface_name }}", ): {% filter indent(first=True) %} {% if interface.has_members %} {% for method in interface.methods %} {% include 'blocking_method' %} {% endfor %} {% for a_property in interface.properties %} {% include 'blocking_property' %} {% endfor %} {% else %} {% include 'generic_no_members' %} {% endif %} {% endfilter %} """ ), "blocking_method": ( """\ @dbus_method( {% filter indent(first=True) %} {% include 'generic_method_flags' %} {% endfilter %} ) def {{ method.python_name }}( self, {% for arg_name, arg_type in method.args_names_and_typing %} {{ arg_name }}: {{ arg_type }}, {% endfor %} ) -> {{ method.result_typing }}: raise NotImplementedError """ ), "blocking_property": ( """\ @dbus_property( {% filter indent(first=True) %} {% include 'generic_property_flags' %} {% endfilter %} ) def {{ a_property.python_name }}(self) -> {{ a_property.typing }}: raise NotImplementedError """ ), } def xml_to_interfaces_introspection( root: Element) -> list[DbusInterfaceIntrospection]: list_of_interface_introspection: list[DbusInterfaceIntrospection] = [] if root.tag != 'node': raise ValueError(f"Expected node tag got {root.tag}") for interface in root: if interface.tag == 'node': continue if interface.attrib['name'] in SKIP_INTERFACES: continue list_of_interface_introspection.append( DbusInterfaceIntrospection(interface)) return list_of_interface_introspection def interfaces_from_file(filename_or_path: Union[str, Path] ) -> list[DbusInterfaceIntrospection]: etree = etree_from_file(filename_or_path) return xml_to_interfaces_introspection(etree.getroot()) def interfaces_from_str(xml_str: str) -> list[DbusInterfaceIntrospection]: etree = etree_from_str(xml_str) return xml_to_interfaces_introspection(etree) def generate_py_file( interfaces: list[DbusInterfaceIntrospection], include_import_header: bool = True, do_async: bool = True, ) -> str: from jinja2 import DictLoader from jinja2 import Environment as JinjaEnv template_name = "async_main" if do_async else "blocking_main" env = JinjaEnv( loader=DictLoader(INTERFACE_TEMPLATES), trim_blocks=True, lstrip_blocks=True, autoescape=False, ) return env.get_template(template_name).render( interfaces=interfaces, include_import_header=include_import_header, ) python-sdbus-0.14.0/src/sdbus/meson.build000066400000000000000000000013301477456016000203130ustar00rootroot00000000000000sd_bus_internals_sources = files( './sd_bus_internals.c', './sd_bus_internals_bus.c', './sd_bus_internals_funcs.c', './sd_bus_internals_interface.c', './sd_bus_internals_message.c', './sd_bus_internals.h', ) python3_dep = dependency('python3', version : '>= 3.7') c_compiler = meson.get_compiler('c') lint_args = ['-Wall', '-Wextra', '-Werror'] sd_bus_internals_module = shared_module( 'sd_bus_internals', sd_bus_internals_sources, dependencies : python3_dep, c_args : lint_args, ) sd_bus_internals_module_stable = shared_module( 'sd_bus_internals_stable', sd_bus_internals_sources, dependencies : python3_dep, c_args : lint_args + ['-DPy_LIMITED_API=0x03070000'], ) python-sdbus-0.14.0/src/sdbus/py.typed000066400000000000000000000000541477456016000176520ustar00rootroot00000000000000# Mark the package as supporting type hints python-sdbus-0.14.0/src/sdbus/sd_bus_internals.c000066400000000000000000000245771477456016000216750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sd_bus_internals.h" // Python functions and objects PyObject* asyncio_get_running_loop = NULL; PyObject* is_coroutine_function = NULL; // Str objects PyObject* set_result_str = NULL; PyObject* set_exception_str = NULL; PyObject* add_reader_str = NULL; PyObject* remove_reader_str = NULL; PyObject* add_writer_str = NULL; PyObject* remove_writer_str = NULL; PyObject* empty_str = NULL; PyObject* null_str = NULL; PyObject* extend_str = NULL; PyObject* append_str = NULL; PyObject* call_soon_str = NULL; PyObject* create_task_str = NULL; // Exceptions PyObject* exception_base = NULL; PyObject* unmapped_error_exception = NULL; PyObject* exception_lib = NULL; PyObject* exception_request_name = NULL; // Base to any request name exception PyObject* exception_request_name_in_queue = NULL; // Queued up to acquire name PyObject* exception_request_name_exists = NULL; // Someone already owns the name PyObject* exception_request_name_already_owner = NULL; // Already an owner of the name PyObject* dbus_error_to_exception_dict = NULL; PyObject* exception_to_dbus_error_dict = NULL; // SdBusSlot static void SdBusSlot_dealloc(SdBusSlotObject* self) { sd_bus_slot_unref(self->slot_ref); SD_BUS_DEALLOC_TAIL; } static PyObject* SdBusSlot_close(SdBusSlotObject* self, PyObject* Py_UNUSED(args)) { sd_bus_slot_unref(self->slot_ref); self->slot_ref = NULL; Py_RETURN_NONE; } static PyMethodDef SdBusSlot_methods[] = { {"close", (PyCFunction)SdBusSlot_close, METH_NOARGS, PyDoc_STR("Dereference sd-bus slot stopping any associated callbacks.")}, {NULL, NULL, 0, NULL}, }; PyType_Spec SdBusSlotType = { .name = "sd_bus_internals.SdBusSlot", .basicsize = sizeof(SdBusSlotObject), .itemsize = 0, .flags = Py_TPFLAGS_DEFAULT, .slots = (PyType_Slot[]){ {Py_tp_new, PyType_GenericNew}, {Py_tp_dealloc, (destructor)SdBusSlot_dealloc}, {Py_tp_methods, SdBusSlot_methods}, {0, NULL}, }, }; static PyModuleDef sd_bus_internals_module = { PyModuleDef_HEAD_INIT, .m_name = "sd_bus_internals", .m_doc = PyDoc_STR("Sd bus internals module."), .m_methods = SdBusPyInternal_methods, .m_size = -1, }; PyObject* SdBus_class = NULL; PyObject* SdBusMessage_class = NULL; PyObject* SdBusSlot_class = NULL; PyObject* SdBusInterface_class = NULL; #define SD_BUS_PY_INIT_TYPE_READY(type_slots) \ ({ \ PyObject* class = PyType_FromSpecWithBases(&type_slots, NULL); \ if (class == NULL) { \ return NULL; \ } \ class; \ }) #define SD_BUS_PY_INIT_ADD_OBJECT(type_name, class) \ if (PyModule_AddObject(m, type_name, (PyObject*)class) < 0) { \ Py_DECREF((PyObject*)class); \ return NULL; \ } PyMODINIT_FUNC PyInit_sd_bus_internals(void) { PyObject* m CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyModule_Create(&sd_bus_internals_module)); SdBus_class = SD_BUS_PY_INIT_TYPE_READY(SdBusType); SD_BUS_PY_INIT_ADD_OBJECT("SdBus", SdBus_class); SdBusMessage_class = SD_BUS_PY_INIT_TYPE_READY(SdBusMessageType); SD_BUS_PY_INIT_ADD_OBJECT("SdBusMessage", SdBusMessage_class); SdBusSlot_class = SD_BUS_PY_INIT_TYPE_READY(SdBusSlotType); SD_BUS_PY_INIT_ADD_OBJECT("SdBusSlot", SdBusSlot_class); SdBusInterface_class = SD_BUS_PY_INIT_TYPE_READY(SdBusInterfaceType); SD_BUS_PY_INIT_ADD_OBJECT("SdBusInterface", SdBusInterface_class); // Exception map dbus_error_to_exception_dict = CALL_PYTHON_AND_CHECK(PyDict_New()); SD_BUS_PY_INIT_ADD_OBJECT("DBUS_ERROR_TO_EXCEPTION", dbus_error_to_exception_dict); exception_to_dbus_error_dict = CALL_PYTHON_AND_CHECK(PyDict_New()); SD_BUS_PY_INIT_ADD_OBJECT("EXCEPTION_TO_DBUS_ERROR", exception_to_dbus_error_dict); PyObject* new_base_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusBaseError", NULL, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusBaseError", new_base_exception); exception_base = new_base_exception; PyObject* new_unmapped_error_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusUnmappedMessageError", new_base_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusUnmappedMessageError", new_unmapped_error_exception); unmapped_error_exception = new_unmapped_error_exception; PyObject* library_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusLibraryError", new_base_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusLibraryError", library_exception); exception_lib = library_exception; // Request name exceptions PyObject* request_name_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusRequestNameError", new_base_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusRequestNameError", request_name_exception); exception_request_name = request_name_exception; // Request name but in queue PyObject* request_name_in_queue_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusRequestNameInQueueError", request_name_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusRequestNameInQueueError", request_name_in_queue_exception); exception_request_name_in_queue = request_name_in_queue_exception; // Request name but someone already owns the name PyObject* request_name_exists_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusRequestNameExistsError", request_name_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusRequestNameExistsError", request_name_exists_exception); exception_request_name_exists = request_name_exists_exception; // Request name but we already own the name PyObject* request_name_already_owner_exception CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyErr_NewException("sd_bus_internals.SdBusRequestNameAlreadyOwnerError", request_name_exception, NULL)); SD_BUS_PY_INIT_ADD_OBJECT("SdBusRequestNameAlreadyOwnerError", request_name_already_owner_exception); exception_request_name_already_owner = request_name_already_owner_exception; PyObject* asyncio_module = CALL_PYTHON_AND_CHECK(PyImport_ImportModule("asyncio")); asyncio_get_running_loop = CALL_PYTHON_AND_CHECK(PyObject_GetAttrString(asyncio_module, "get_running_loop")); set_result_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("set_result")); set_exception_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("set_exception")); call_soon_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("call_soon")); create_task_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("create_task")); remove_reader_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("remove_reader")); add_reader_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("add_reader")); add_writer_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("add_writer")); remove_writer_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("remove_writer")); empty_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("")); null_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromStringAndSize("\0", 1)); extend_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("extend")); append_str = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("append")); PyObject* inspect_module = CALL_PYTHON_AND_CHECK(PyImport_ImportModule("inspect")); is_coroutine_function = CALL_PYTHON_AND_CHECK(PyObject_GetAttrString(inspect_module, "iscoroutinefunction")); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusDeprecatedFlag", SD_BUS_VTABLE_DEPRECATED)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusHiddenFlag", SD_BUS_VTABLE_HIDDEN)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusUnprivilegedFlag", SD_BUS_VTABLE_UNPRIVILEGED)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusNoReplyFlag", SD_BUS_VTABLE_METHOD_NO_REPLY)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusPropertyConstFlag", SD_BUS_VTABLE_PROPERTY_CONST)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusPropertyEmitsChangeFlag", SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusPropertyEmitsInvalidationFlag", SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusPropertyExplicitFlag", SD_BUS_VTABLE_PROPERTY_EXPLICIT)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "DbusSensitiveFlag", SD_BUS_VTABLE_SENSITIVE)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "NameAllowReplacementFlag", SD_BUS_NAME_ALLOW_REPLACEMENT)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "NameReplaceExistingFlag", SD_BUS_NAME_REPLACE_EXISTING)); CALL_PYTHON_INT_CHECK(PyModule_AddIntConstant(m, "NameQueueFlag", SD_BUS_NAME_QUEUE)); Py_INCREF(m); return m; } python-sdbus-0.14.0/src/sdbus/sd_bus_internals.h000066400000000000000000000343571477456016000216770ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #define PY_SSIZE_T_CLEAN #include #include #include // Macros #define SD_BUS_PY_CHECK_ARGS_NUMBER(number_args) \ if (nargs != number_args) { \ PyErr_Format(PyExc_TypeError, "Expected " #number_args " arguments, got %i", nargs); \ return NULL; \ } #define SD_BUS_PY_CHECK_ARG_CHECK_FUNC(arg_num, arg_check_function) \ if (!arg_check_function(args[arg_num])) { \ PyErr_SetString(PyExc_TypeError, "Argument failed a " #arg_check_function " check"); \ return NULL; \ } // Call Python macros #define CALL_PYTHON_FAIL_ACTION(py_function, action) \ ({ \ PyObject* new_object = py_function; \ if (new_object == NULL) { \ action; \ } \ new_object; \ }) #define CALL_PYTHON_AND_CHECK(py_function) CALL_PYTHON_FAIL_ACTION(py_function, return NULL) #define CALL_PYTHON_CHECK_RETURN_NEG1(py_function) CALL_PYTHON_FAIL_ACTION(py_function, return -1) #define CALL_PYTHON_GOTO_FAIL(py_function) CALL_PYTHON_FAIL_ACTION(py_function, goto fail) #define CALL_PYTHON_INT_CHECK(py_function) \ ({ \ int return_int = py_function; \ if (return_int < 0) { \ return NULL; \ } \ return_int; \ }) #define CALL_PYTHON_BOOL_CHECK(py_function) \ ({ \ int return_int = py_function; \ if (!return_int) { \ return NULL; \ } \ return_int; \ }) #define CALL_PYTHON_EXPECT_NONE(py_function) \ ({ \ PyObject* none_obj = py_function; \ if (none_obj == NULL) { \ return NULL; \ } \ Py_DECREF(none_obj); \ }) #define PYTHON_ERR_OCCURED \ if (PyErr_Occurred()) { \ return NULL; \ } #define SDBUS_LIBRARY_ERROR_FORMAT(func_call) \ PyErr_Format(exception_lib, \ "File: %s Line: %d. " #func_call \ " in function %s returned " \ "error number: %i", \ __FILE__, __LINE__, __FUNCTION__, -return_int) #define CALL_SD_BUS_AND_CHECK(sd_bus_function) \ ({ \ int return_int = sd_bus_function; \ if (return_int < 0) { \ SDBUS_LIBRARY_ERROR_FORMAT(sd_bus_function); \ return NULL; \ } \ return_int; \ }) #define CALL_SD_BUS_CHECK_RETURN_NEG1(sd_bus_function) \ ({ \ int return_int = sd_bus_function; \ if (return_int < 0) { \ SDBUS_LIBRARY_ERROR_FORMAT(sd_bus_function); \ return -1; \ } \ return_int; \ }) #define SD_BUS_PY_UNICODE_AS_BYTES_ERROR_ACTION(py_unicode, action) \ ({ \ PyObject* utf_8_bytes = PyUnicode_AsUTF8String(py_unicode); \ if (utf_8_bytes == NULL) { \ action; \ } \ utf_8_bytes; \ }) #define SD_BUS_PY_UNICODE_AS_BYTES(py_unicode) SD_BUS_PY_UNICODE_AS_BYTES_ERROR_ACTION(py_unicode, return NULL) #define SD_BUS_PY_UNICODE_AS_BYTES_GOTO_FAIL(py_unicode) SD_BUS_PY_UNICODE_AS_BYTES_ERROR_ACTION(py_unicode, goto fail) #define SD_BUS_PY_BYTES_AS_CHAR_PTR_ERROR_ACTION(py_bytes, action) \ ({ \ const char* new_char_ptr = PyBytes_AsString(py_bytes); \ if (new_char_ptr == NULL) { \ action; \ } \ new_char_ptr; \ }) #define SD_BUS_PY_BYTES_AS_CHAR_PTR(py_bytes) SD_BUS_PY_BYTES_AS_CHAR_PTR_ERROR_ACTION(py_bytes, return NULL) #define SD_BUS_PY_BYTES_AS_CHAR_PTR_GOTO_FAIL(py_bytes) SD_BUS_PY_BYTES_AS_CHAR_PTR_ERROR_ACTION(py_bytes, goto fail) #ifndef Py_LIMITED_API #define SD_BUS_PY_UNICODE_AS_CHAR_PTR_ERROR_ACTION(py_object, action) \ ({ \ const char* new_char_ptr = PyUnicode_AsUTF8(py_object); \ if (new_char_ptr == NULL) { \ action; \ } \ new_char_ptr; \ }) #define SD_BUS_PY_UNICODE_AS_CHAR_PTR(py_object) SD_BUS_PY_UNICODE_AS_CHAR_PTR_ERROR_ACTION(py_object, return NULL) #define SD_BUS_PY_UNICODE_AS_CHAR_PTR_GOTO_FAIL(py_object) SD_BUS_PY_UNICODE_AS_CHAR_PTR_ERROR_ACTION(py_object, goto fail) #define SD_BUS_PY_UNICODE_AS_CHAR_PTR_OPTIONAL(py_object) \ ({ \ const char* new_char_ptr_or_null = NULL; \ if (Py_None != py_object) { \ new_char_ptr_or_null = SD_BUS_PY_UNICODE_AS_CHAR_PTR(py_object); \ } \ new_char_ptr_or_null; \ }) #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_CLASS_DUNDER_NEW(py_class) ({ (((PyTypeObject*)py_class)->tp_new(((PyTypeObject*)py_class), NULL, NULL)); }) #else #define SD_BUS_PY_CLASS_DUNDER_NEW(py_class) \ ({ \ PyObject* (*dunder_new_func)(PyTypeObject*, PyObject*, PyObject*) = (newfunc)PyType_GetSlot((PyTypeObject*)py_class, Py_tp_new); \ dunder_new_func((PyTypeObject*)py_class, NULL, NULL); \ }) #endif #define CALL_PYTHON_ITER(iter, iter_end) \ ({ \ PyObject* next_object = PyIter_Next(signature_iter); \ if (next_object == NULL) \ \ { \ if (PyErr_Occurred()) { \ return NULL; \ } else { \ iter_end; \ } \ } \ next_object; \ }) #ifndef Py_LIMITED_API #define SD_BUS_DEALLOC_TAIL \ PyTypeObject* self_type = Py_TYPE(self); \ self_type->tp_free(self); \ Py_DECREF(self_type); #else #define SD_BUS_DEALLOC_TAIL \ PyTypeObject* self_type = Py_TYPE(self); \ void (*free_self)(void*) = (freefunc)PyType_GetSlot(self_type, Py_tp_free); \ free_self(self); \ Py_DECREF(self_type); #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_METH METH_FASTCALL #else #define SD_BUS_PY_METH METH_VARARGS #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_FUNC_TYPE void* #else #define SD_BUS_PY_FUNC_TYPE PyCFunction #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_LIST_GET_ITEM PyList_GET_ITEM #else #define SD_BUS_PY_LIST_GET_ITEM PyList_GetItem #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_TUPLE_GET_SIZE PyTuple_GET_SIZE #else #define SD_BUS_PY_TUPLE_GET_SIZE PyTuple_Size #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_TUPLE_GET_ITEM PyTuple_GET_ITEM #else #define SD_BUS_PY_TUPLE_GET_ITEM PyTuple_GetItem #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_TUPLE_SET_ITEM PyTuple_SET_ITEM #else #define SD_BUS_PY_TUPLE_SET_ITEM PyTuple_SetItem #endif #ifndef Py_LIMITED_API #define SD_BUS_PY_LIST_GET_SIZE PyList_GET_SIZE #else #define SD_BUS_PY_LIST_GET_SIZE PyList_Size #endif // Python functions and objects extern PyObject* asyncio_get_running_loop; extern PyObject* is_coroutine_function; // Str objects extern PyObject* set_result_str; extern PyObject* set_exception_str; extern PyObject* add_reader_str; extern PyObject* remove_reader_str; extern PyObject* add_writer_str; extern PyObject* remove_writer_str; extern PyObject* empty_str; extern PyObject* null_str; extern PyObject* extend_str; extern PyObject* append_str; extern PyObject* call_soon_str; extern PyObject* create_task_str; // Exceptions extern PyObject* exception_base; extern PyObject* unmapped_error_exception; extern PyObject* exception_lib; extern PyObject* exception_request_name; // Base to any request name exception extern PyObject* exception_request_name_in_queue; // Queued up to acquire name extern PyObject* exception_request_name_exists; // Someone already owns the name extern PyObject* exception_request_name_already_owner; // Already an owner of the name extern PyObject* dbus_error_to_exception_dict; extern PyObject* exception_to_dbus_error_dict; __attribute__((used)) static inline void _cleanup_char_ptr(const char** ptr) { if (*ptr != NULL) { free((char*)*ptr); } } #define CLEANUP_STR_MALLOC __attribute__((cleanup(_cleanup_char_ptr))) __attribute__((used)) static inline void PyObject_cleanup(PyObject** object) { Py_XDECREF(*object); } #define CLEANUP_PY_OBJECT __attribute__((cleanup(PyObject_cleanup))) // SdBusSlot typedef struct { PyObject_HEAD; sd_bus_slot* slot_ref; } SdBusSlotObject; __attribute__((used)) static inline void cleanup_SdBusSlot(SdBusSlotObject** object) { Py_XDECREF(*object); } #define CLEANUP_SD_BUS_SLOT __attribute__((cleanup(cleanup_SdBusSlot))) extern PyType_Spec SdBusSlotType; extern PyObject* SdBusSlot_class; // SdBusInterface typedef struct { PyObject_HEAD; SdBusSlotObject* interface_slot; PyObject* method_list; PyObject* method_dict; PyObject* property_list; PyObject* property_get_dict; PyObject* property_set_dict; PyObject* signal_list; sd_bus_vtable* vtable; } SdBusInterfaceObject; extern PyType_Spec SdBusInterfaceType; extern PyObject* SdBusInterface_class; // SdBusMessage typedef struct { PyObject_HEAD; sd_bus_message* message_ref; } SdBusMessageObject; __attribute__((used)) static inline void cleanup_SdBusMessage(SdBusMessageObject** object) { Py_XDECREF(*object); } extern void _SdBusMessage_set_messsage(SdBusMessageObject* self, sd_bus_message* new_message); #define CLEANUP_SD_BUS_MESSAGE __attribute__((cleanup(cleanup_SdBusMessage))) extern PyType_Spec SdBusMessageType; extern PyObject* SdBusMessage_class; // SdBus typedef struct { PyObject_HEAD; sd_bus* sd_bus_ref; PyObject* bus_fd; PyObject* loop; PyObject* timer_fd; int asyncio_watchers_last_state; int timer_fd_int; } SdBusObject; extern PyType_Spec SdBusType; extern PyObject* SdBus_class; // Module level functions extern PyMethodDef SdBusPyInternal_methods[]; python-sdbus-0.14.0/src/sdbus/sd_bus_internals.py000066400000000000000000000224041477456016000220660ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import Future from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Callable, Coroutine, Sequence from typing import Any, Optional, Union DbusBasicTypes = Union[str, int, bytes, float, Any] DbusStructType = tuple[DbusBasicTypes, ...] DbusDictType = dict[DbusBasicTypes, DbusBasicTypes] DbusVariantType = tuple[str, DbusStructType] DbusListType = list[DbusBasicTypes] DbusCompleteTypes = Union[DbusBasicTypes, DbusStructType, DbusDictType, DbusVariantType, DbusListType] __STUB_ERROR = ( 'Typing stub. You should never see this ' 'error unless the actual module failed to load. ' 'Check your installation.' ) class SdBusSlot: """Holds reference to SdBus slot""" def close(self) -> None: raise NotImplementedError(__STUB_ERROR) class SdBusInterface: slot: Optional[SdBusSlot] method_list: list[object] method_dict: dict[bytes, object] property_list: list[object] property_get_dict: dict[bytes, object] property_set_dict: dict[bytes, object] signal_list: list[object] def add_method( self, member_name: str, signature: str, input_args_names: Sequence[str], result_signature: str, result_args_names: Sequence[str], flags: int, callback: Callable[[SdBusMessage], Coroutine[Any, Any, None]], / ) -> None: raise NotImplementedError(__STUB_ERROR) def add_property( self, property_name: str, property_signature: str, get_function: Callable[[SdBusMessage], Any], set_function: Optional[Callable[[SdBusMessage], None]], flags: int, / ) -> None: raise NotImplementedError(__STUB_ERROR) def add_signal( self, signal_name: str, signal_signature: str, signal_args_names: Sequence[str], flags: int, / ) -> None: raise NotImplementedError(__STUB_ERROR) class SdBusMessage: def append_data(self, signature: str, *args: DbusCompleteTypes) -> None: raise NotImplementedError(__STUB_ERROR) def open_container(self, container_type: str, container_signature: str, /) -> None: raise NotImplementedError(__STUB_ERROR) def close_container(self) -> None: raise NotImplementedError(__STUB_ERROR) def enter_container(self, container_type: str, container_signature: str, /) -> None: raise NotImplementedError(__STUB_ERROR) def exit_container(self) -> None: raise NotImplementedError(__STUB_ERROR) def dump(self) -> None: raise NotImplementedError(__STUB_ERROR) def seal(self) -> None: raise NotImplementedError(__STUB_ERROR) def get_contents(self ) -> tuple[DbusCompleteTypes, ...]: raise NotImplementedError(__STUB_ERROR) def create_reply(self) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def create_error_reply( self, error_name: str, error_message: str, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def send(self) -> None: raise NotImplementedError(__STUB_ERROR) def parse_to_tuple(self) -> tuple[Any, ...]: raise NotImplementedError(__STUB_ERROR) expect_reply: bool = False destination: Optional[str] = None path: Optional[str] = None interface: Optional[str] = None member: Optional[str] = None sender: Optional[str] = None class SdBus: def call(self, message: SdBusMessage, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def call_async( self, message: SdBusMessage, /) -> Future[SdBusMessage]: raise NotImplementedError(__STUB_ERROR) def process(self) -> None: raise NotImplementedError(__STUB_ERROR) def get_fd(self) -> int: raise NotImplementedError(__STUB_ERROR) def new_method_call_message( self, destination_name: str, object_path: str, interface_name: str, member_name: str, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def new_property_get_message( self, destination_service_name: str, object_path: str, interface_name: str, member_name: str, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def new_property_set_message( self, destination_service_name: str, object_path: str, interface_name: str, member_name: str, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def new_signal_message( self, object_path: str, interface_name: str, member_name: str, /) -> SdBusMessage: raise NotImplementedError(__STUB_ERROR) def add_interface(self, new_interface: SdBusInterface, object_path: str, interface_name: str, /) -> None: raise NotImplementedError(__STUB_ERROR) def match_signal_async( self, senders_name: Optional[str], object_path: Optional[str], interface_name: Optional[str], member_name: Optional[str], callback: Callable[[SdBusMessage], None], / ) -> Future[SdBusSlot]: raise NotImplementedError(__STUB_ERROR) def request_name_async(self, name: str, flags: int, /) -> Future[None]: raise NotImplementedError(__STUB_ERROR) def request_name(self, name: str, flags: int, /) -> None: raise NotImplementedError(__STUB_ERROR) def add_object_manager(self, path: str, /) -> SdBusSlot: raise NotImplementedError(__STUB_ERROR) def emit_object_added(self, path: str, /) -> None: raise NotImplementedError(__STUB_ERROR) def emit_object_removed(self, path: str, /) -> None: raise NotImplementedError(__STUB_ERROR) def close(self) -> None: raise NotImplementedError(__STUB_ERROR) def start(self) -> None: raise NotImplementedError(__STUB_ERROR) address: Optional[str] = None method_call_timeout_usec: int = 0 def sd_bus_open() -> SdBus: raise NotImplementedError(__STUB_ERROR) def sd_bus_open_user() -> SdBus: raise NotImplementedError(__STUB_ERROR) def sd_bus_open_system() -> SdBus: raise NotImplementedError(__STUB_ERROR) def sd_bus_open_system_remote(host: str, /) -> SdBus: raise NotImplementedError(__STUB_ERROR) def sd_bus_open_user_machine(machine: str, /) -> SdBus: raise NotImplementedError(__STUB_ERROR) def sd_bus_open_system_machine(machine: str, /) -> SdBus: raise NotImplementedError(__STUB_ERROR) def encode_object_path(prefix: str, external: str) -> str: raise NotImplementedError(__STUB_ERROR) def decode_object_path(prefix: str, full_path: str) -> str: raise NotImplementedError(__STUB_ERROR) def map_exception_to_dbus_error(exc: type[Exception], dbus_error_name: str, /) -> None: ... # We want to be able to generate docs without module def add_exception_mapping(exc: Exception, /) -> None: ... # We want to be able to generate docs without module def is_interface_name_valid(string_to_check: str, /) -> bool: raise NotImplementedError(__STUB_ERROR) def is_service_name_valid(string_to_check: str, /) -> bool: raise NotImplementedError(__STUB_ERROR) def is_member_name_valid(string_to_check: str, /) -> bool: raise NotImplementedError(__STUB_ERROR) def is_object_path_valid(string_to_check: str, /) -> bool: raise NotImplementedError(__STUB_ERROR) class SdBusBaseError(Exception): ... class SdBusUnmappedMessageError(SdBusBaseError): ... class SdBusLibraryError(SdBusBaseError): ... class SdBusRequestNameError(SdBusBaseError): ... class SdBusRequestNameInQueueError(SdBusRequestNameError): ... class SdBusRequestNameExistsError(SdBusRequestNameError): ... class SdBusRequestNameAlreadyOwnerError(SdBusRequestNameError): ... DBUS_ERROR_TO_EXCEPTION: dict[str, type[Exception]] = {} EXCEPTION_TO_DBUS_ERROR: dict[type[Exception], str] = {} DbusDeprecatedFlag: int = 0 DbusHiddenFlag: int = 0 DbusUnprivilegedFlag: int = 0 DbusNoReplyFlag: int = 0 DbusPropertyConstFlag: int = 0 DbusPropertyEmitsChangeFlag: int = 0 DbusPropertyEmitsInvalidationFlag: int = 0 DbusPropertyExplicitFlag: int = 0 DbusSensitiveFlag: int = 0 NameAllowReplacementFlag: int = 0 NameReplaceExistingFlag: int = 0 NameQueueFlag: int = 0 python-sdbus-0.14.0/src/sdbus/sd_bus_internals_bus.c000066400000000000000000001147521477456016000225410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "sd_bus_internals.h" static void SdBus_dealloc(SdBusObject* self) { if (NULL != self->loop && NULL != self->bus_fd) { Py_XDECREF(PyObject_CallMethodObjArgs(self->loop, remove_reader_str, self->bus_fd, NULL)); Py_XDECREF(PyObject_CallMethodObjArgs(self->loop, remove_writer_str, self->bus_fd, NULL)); } if (NULL != self->timer_fd) { Py_XDECREF(PyObject_CallMethodObjArgs(self->loop, remove_reader_str, self->timer_fd, NULL)); Py_DECREF(self->timer_fd); close(self->timer_fd_int); } sd_bus_unref(self->sd_bus_ref); Py_XDECREF(self->bus_fd); Py_XDECREF(self->loop); SD_BUS_DEALLOC_TAIL; } static int SdBus_init(SdBusObject* self, PyObject* Py_UNUSED(args), PyObject* Py_UNUSED(kwds)) { CALL_SD_BUS_CHECK_RETURN_NEG1(sd_bus_new(&(self->sd_bus_ref))); return 0; } #ifndef Py_LIMITED_API static SdBusMessageObject* SdBus_new_method_call_message(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(4); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, PyUnicode_Check); const char* destination_bus_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); const char* interface_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[2]); const char* member_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[3]); #else static SdBusMessageObject* SdBus_new_method_call_message(SdBusObject* self, PyObject* args) { const char* destination_bus_name = NULL; const char* object_path = NULL; const char* interface_name = NULL; const char* member_name = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ssss", &destination_bus_name, &object_path, &interface_name, &member_name, NULL)); #endif SdBusMessageObject* new_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK( sd_bus_message_new_method_call(self->sd_bus_ref, &new_message_object->message_ref, destination_bus_name, object_path, interface_name, member_name)); Py_INCREF(new_message_object); return new_message_object; } #ifndef Py_LIMITED_API static SdBusMessageObject* SdBus_new_property_get_message(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(4); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, PyUnicode_Check); const char* destination_service_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); const char* interface_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[2]); const char* property_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[3]); #else static SdBusMessageObject* SdBus_new_property_get_message(SdBusObject* self, PyObject* args) { const char* destination_service_name = NULL; const char* object_path = NULL; const char* interface_name = NULL; const char* property_name = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ssss", &destination_service_name, &object_path, &interface_name, &property_name, NULL)); #endif SdBusMessageObject* new_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK(sd_bus_message_new_method_call(self->sd_bus_ref, &new_message_object->message_ref, destination_service_name, object_path, "org.freedesktop.DBus.Properties", "Get")); // Add property_name CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(new_message_object->message_ref, 's', interface_name)); CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(new_message_object->message_ref, 's', property_name)); Py_INCREF(new_message_object); return new_message_object; } #ifndef Py_LIMITED_API static SdBusMessageObject* SdBus_new_property_set_message(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(4); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, PyUnicode_Check); const char* destination_service_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); const char* interface_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[2]); const char* property_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[3]); #else static SdBusMessageObject* SdBus_new_property_set_message(SdBusObject* self, PyObject* args) { const char* destination_service_name = NULL; const char* object_path = NULL; const char* interface_name = NULL; const char* property_name = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ssss", &destination_service_name, &object_path, &interface_name, &property_name, NULL)); #endif SdBusMessageObject* new_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK(sd_bus_message_new_method_call(self->sd_bus_ref, &new_message_object->message_ref, destination_service_name, object_path, "org.freedesktop.DBus.Properties", "Set")); // Add property_name CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(new_message_object->message_ref, 's', interface_name)); CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(new_message_object->message_ref, 's', property_name)); Py_INCREF(new_message_object); return new_message_object; } #ifndef Py_LIMITED_API static SdBusMessageObject* SdBus_new_signal_message(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(3); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); // Path SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); // Interface SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyUnicode_Check); // Member const char* object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* interface_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); const char* member_name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[2]); #else static SdBusMessageObject* SdBus_new_signal_message(SdBusObject* self, PyObject* args) { char* object_path = NULL; const char* interface_name = NULL; const char* member_name = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "sss", &object_path, &interface_name, &member_name, NULL)); #endif SdBusMessageObject* new_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK(sd_bus_message_new_signal(self->sd_bus_ref, &new_message_object->message_ref, object_path, interface_name, member_name)); Py_INCREF(new_message_object); return new_message_object; } #ifndef Py_LIMITED_API static int _check_sdbus_message(PyObject* something) { return PyType_IsSubtype(Py_TYPE(something), (PyTypeObject*)SdBusMessage_class); } static SdBusMessageObject* SdBus_call(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { // TODO: Check reference counting SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, _check_sdbus_message); SdBusMessageObject* call_message = (SdBusMessageObject*)args[0]; #else static SdBusMessageObject* SdBus_call(SdBusObject* self, PyObject* args) { SdBusMessageObject* call_message = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "O", &call_message, NULL)); #endif SdBusMessageObject* reply_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); sd_bus_error error __attribute__((cleanup(sd_bus_error_free))) = SD_BUS_ERROR_NULL; int return_value = sd_bus_call(self->sd_bus_ref, call_message->message_ref, (uint64_t)0, &error, &reply_message_object->message_ref); if (sd_bus_error_get_errno(&error)) { PyObject* error_name_str CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_FromString(error.name)); PyObject* exception_to_raise = PyDict_GetItemWithError(dbus_error_to_exception_dict, error_name_str); if (PyErr_Occurred()) { return NULL; } if (exception_to_raise == NULL) { PyObject* exception_tuple CLEANUP_PY_OBJECT = Py_BuildValue("(ss)", error.name, error.message); PyErr_SetObject(unmapped_error_exception, exception_tuple); return NULL; } else { PyErr_SetString(exception_to_raise, error.message); return NULL; } } CALL_SD_BUS_AND_CHECK(return_value); Py_INCREF(reply_message_object); return reply_message_object; } int future_set_exception_from_message(PyObject* future, sd_bus_message* message) { const sd_bus_error* callback_error = sd_bus_message_get_error(message); PyObject* error_name_str CLEANUP_PY_OBJECT = CALL_PYTHON_CHECK_RETURN_NEG1(PyUnicode_FromString(callback_error->name)); PyObject* error_message_str CLEANUP_PY_OBJECT = CALL_PYTHON_CHECK_RETURN_NEG1(PyUnicode_FromString(callback_error->message)); PyObject* exception_to_raise = PyDict_GetItemWithError(dbus_error_to_exception_dict, error_name_str); PyObject* exception_occurred = PyErr_Occurred(); if (exception_occurred) { Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethodObjArgs(future, set_exception_str, exception_occurred, NULL))); return 0; } if (exception_to_raise) { PyObject* new_exception CLEANUP_PY_OBJECT = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(exception_to_raise, error_message_str, NULL)); Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethodObjArgs(future, set_exception_str, new_exception, NULL))); } else { PyObject* new_exception CLEANUP_PY_OBJECT = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(unmapped_error_exception, error_name_str, error_message_str, NULL)); Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethodObjArgs(future, set_exception_str, new_exception, NULL))); } return 0; } static PyObject* SdBus_get_fd(SdBusObject* self, PyObject* Py_UNUSED(args)) { int file_descriptor = CALL_SD_BUS_AND_CHECK(sd_bus_get_fd(self->sd_bus_ref)); return PyLong_FromLong((long)file_descriptor); } static PyObject* SdBus_asyncio_update_fd_watchers(SdBusObject* self); #define CHECK_ASYNCIO_WATCHERS ({ CALL_PYTHON_EXPECT_NONE(SdBus_asyncio_update_fd_watchers(self)); }) static PyObject* _get_or_bind_loop(SdBusObject* self) { if (NULL == self->loop) { self->loop = CALL_PYTHON_AND_CHECK(PyObject_CallFunctionObjArgs(asyncio_get_running_loop, NULL)); } return self->loop; } static PyObject* SdBus_process(SdBusObject* self, PyObject* Py_UNUSED(args)) { int return_value = 1; while (return_value > 0) { return_value = sd_bus_process(self->sd_bus_ref, NULL); if (return_value < 0) { if (-ECONNRESET == return_value) { // Connection gracefully terminated break; } else { // Error occurred processing sdbus CALL_SD_BUS_AND_CHECK(return_value); return NULL; } } if (PyErr_Occurred()) { return NULL; } } CHECK_ASYNCIO_WATCHERS; Py_RETURN_NONE; } int SdBus_async_callback(sd_bus_message* m, void* userdata, // Should be the asyncio.Future sd_bus_error* Py_UNUSED(ret_error)) { sd_bus_message* reply_message __attribute__((cleanup(sd_bus_message_unrefp))) = sd_bus_message_ref(m); PyObject* py_future = userdata; PyObject* is_cancelled CLEANUP_PY_OBJECT = PyObject_CallMethod(py_future, "cancelled", ""); if (Py_True == is_cancelled) { // A bit unpythonic but SdBus_process does not error out return 0; } if (!sd_bus_message_is_method_error(m, NULL)) { // Not Error, set Future result to new message object SdBusMessageObject* reply_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class); if (reply_message_object == NULL) { return -1; } _SdBusMessage_set_messsage(reply_message_object, reply_message); PyObject* return_object CLEANUP_PY_OBJECT = PyObject_CallMethod(py_future, "set_result", "O", reply_message_object); if (return_object == NULL) { return -1; } } else { // An Error, set exception if (future_set_exception_from_message(py_future, m) < 0) { return -1; } } return 0; } #ifndef Py_LIMITED_API static PyObject* SdBus_call_async(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, _check_sdbus_message); SdBusMessageObject* call_message = (SdBusMessageObject*)args[0]; #else static PyObject* SdBus_call_async(SdBusObject* self, PyObject* args) { SdBusMessageObject* call_message = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "O", &call_message, NULL)); #endif PyObject* running_loop = CALL_PYTHON_AND_CHECK(_get_or_bind_loop(self)); PyObject* new_future = CALL_PYTHON_AND_CHECK(PyObject_CallMethod(running_loop, "create_future", "")); SdBusSlotObject* new_slot_object CLEANUP_SD_BUS_SLOT = (SdBusSlotObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusSlot_class)); CALL_SD_BUS_AND_CHECK( sd_bus_call_async(self->sd_bus_ref, &new_slot_object->slot_ref, call_message->message_ref, SdBus_async_callback, new_future, (uint64_t)0)); if (PyObject_SetAttrString(new_future, "_sd_bus_py_slot", (PyObject*)new_slot_object) < 0) { return NULL; } CHECK_ASYNCIO_WATCHERS; return new_future; } #ifndef Py_LIMITED_API static int _check_is_sdbus_interface(PyObject* type_to_check) { return PyType_IsSubtype(Py_TYPE(type_to_check), (PyTypeObject*)SdBusInterface_class); } static PyObject* SdBus_add_interface(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(3); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, _check_is_sdbus_interface); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyUnicode_Check); SdBusInterfaceObject* interface_object = (SdBusInterfaceObject*)args[0]; const char* path_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); const char* interface_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[2]); #else static PyObject* SdBus_add_interface(SdBusObject* self, PyObject* args) { SdBusInterfaceObject* interface_object = NULL; const char* path_char_ptr = NULL; const char* interface_name_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "Oss", &interface_object, &path_char_ptr, &interface_name_char_ptr, NULL)); #endif PyObject* create_vtable_name CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_FromString("_create_vtable")); Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs((PyObject*)interface_object, create_vtable_name, NULL))); CALL_SD_BUS_AND_CHECK(sd_bus_add_object_vtable(self->sd_bus_ref, &interface_object->interface_slot->slot_ref, path_char_ptr, interface_name_char_ptr, interface_object->vtable, interface_object)); Py_RETURN_NONE; } int _SdBus_signal_callback(sd_bus_message* m, void* userdata, sd_bus_error* Py_UNUSED(ret_error)) { PyObject* signal_callback = userdata; PyObject* running_loop CLEANUP_PY_OBJECT = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(asyncio_get_running_loop, NULL)); SdBusMessageObject* new_message_object CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_CHECK_RETURN_NEG1(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); _SdBusMessage_set_messsage(new_message_object, m); Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethodObjArgs(running_loop, call_soon_str, signal_callback, new_message_object, NULL))); return 0; } int _SdBus_match_signal_instant_callback(sd_bus_message* m, void* userdata, sd_bus_error* Py_UNUSED(ret_error)) { PyObject* new_future = userdata; if (!sd_bus_message_is_method_error(m, NULL)) { SdBusSlotObject* slot_object CLEANUP_SD_BUS_SLOT = (SdBusSlotObject*)CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_GetAttrString(new_future, "_sd_bus_slot")); Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethodObjArgs(new_future, set_result_str, slot_object, NULL))); PyObject* signal_callback = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_GetAttrString(new_future, "_sd_bus_signal_callback")); sd_bus_slot_set_userdata(slot_object->slot_ref, signal_callback); sd_bus_slot_set_destroy_callback(slot_object->slot_ref, (sd_bus_destroy_t)Py_DecRef); } else { if (future_set_exception_from_message(new_future, m) < 0) { return -1; } } return 0; } #ifndef Py_LIMITED_API static int _unicode_or_none(PyObject* some_object) { return (PyUnicode_Check(some_object) || (Py_None == some_object)); } static PyObject* SdBus_match_signal_async(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(5); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, _unicode_or_none); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, _unicode_or_none); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, _unicode_or_none); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, _unicode_or_none); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(4, PyCallable_Check); const char* sender_service_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR_OPTIONAL(args[0]); const char* path_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR_OPTIONAL(args[1]); const char* interface_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR_OPTIONAL(args[2]); const char* member_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR_OPTIONAL(args[3]); PyObject* signal_callback = args[4]; #else static PyObject* SdBus_match_signal_async(SdBusObject* self, PyObject* args) { const char* sender_service_char_ptr = NULL; const char* path_name_char_ptr = NULL; const char* interface_name_char_ptr = NULL; const char* member_name_char_ptr = NULL; PyObject* signal_callback = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "zzzzO", &sender_service_char_ptr, &path_name_char_ptr, &interface_name_char_ptr, &member_name_char_ptr, &signal_callback, NULL)); #endif PyObject* running_loop = CALL_PYTHON_AND_CHECK(_get_or_bind_loop(self)); PyObject* new_future CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyObject_CallMethod(running_loop, "create_future", "")); SdBusSlotObject* new_slot CLEANUP_SD_BUS_SLOT = (SdBusSlotObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusSlot_class)); // Bind lifetime of the slot to the Future CALL_PYTHON_INT_CHECK(PyObject_SetAttrString(new_future, "_sd_bus_slot", (PyObject*)new_slot)); CALL_PYTHON_INT_CHECK(PyObject_SetAttrString(new_future, "_sd_bus_signal_callback", signal_callback)); CALL_SD_BUS_AND_CHECK(sd_bus_match_signal_async(self->sd_bus_ref, &new_slot->slot_ref, sender_service_char_ptr, path_name_char_ptr, interface_name_char_ptr, member_name_char_ptr, _SdBus_signal_callback, _SdBus_match_signal_instant_callback, new_future)); CHECK_ASYNCIO_WATCHERS; Py_INCREF(new_future); return new_future; } int SdBus_request_name_callback(sd_bus_message* m, void* userdata, // Should be the asyncio.Future sd_bus_error* Py_UNUSED(ret_error)) { PyObject* py_future = userdata; PyObject* is_cancelled CLEANUP_PY_OBJECT = PyObject_CallMethod(py_future, "cancelled", ""); if (Py_True == is_cancelled) { // A bit unpythonic but SdBus_process does not error out return 0; } if (!sd_bus_message_is_method_error(m, NULL)) { uint32_t request_name_result = 0; CALL_SD_BUS_CHECK_RETURN_NEG1(sd_bus_message_read_basic(m, 'u', &request_name_result)); if (1 == request_name_result) { // Successfully acquired the name Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethod(py_future, "set_result", "O", Py_None))); return 0; } PyObject* exception_to_raise CLEANUP_PY_OBJECT = NULL; switch (request_name_result) { case 2: exception_to_raise = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(exception_request_name_in_queue, NULL)); break; case 3: exception_to_raise = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(exception_request_name_exists, NULL)); break; case 4: exception_to_raise = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(exception_request_name_already_owner, NULL)); break; default: exception_to_raise = CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallFunctionObjArgs(exception_request_name, NULL)); break; } Py_XDECREF(CALL_PYTHON_CHECK_RETURN_NEG1(PyObject_CallMethod(py_future, "set_exception", "O", exception_to_raise))); return -1; } else { // An Error, set exception if (future_set_exception_from_message(py_future, m) < 0) { return -1; } } return 0; } #ifndef Py_LIMITED_API static PyObject* SdBus_request_name_async(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyLong_Check); const char* service_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); uint64_t flags = PyLong_AsUnsignedLongLong(args[1]); if (PyErr_Occurred()) { return NULL; } #else static PyObject* SdBus_request_name_async(SdBusObject* self, PyObject* args) { const char* service_name_char_ptr = NULL; unsigned long long flags_long_long = 0; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "sK", &service_name_char_ptr, &flags_long_long, NULL)); uint64_t flags = (uint64_t)flags_long_long; #endif PyObject* running_loop = CALL_PYTHON_AND_CHECK(_get_or_bind_loop(self)); PyObject* new_future = CALL_PYTHON_AND_CHECK(PyObject_CallMethod(running_loop, "create_future", "")); SdBusSlotObject* new_slot_object CLEANUP_SD_BUS_SLOT = (SdBusSlotObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusSlot_class)); CALL_SD_BUS_AND_CHECK( sd_bus_request_name_async(self->sd_bus_ref, &new_slot_object->slot_ref, service_name_char_ptr, flags, SdBus_request_name_callback, new_future)); CALL_PYTHON_INT_CHECK(PyObject_SetAttrString(new_future, "_sd_bus_py_slot", (PyObject*)new_slot_object)); CHECK_ASYNCIO_WATCHERS; return new_future; } #ifndef Py_LIMITED_API static PyObject* SdBus_request_name(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyLong_Check); const char* service_name_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); uint64_t flags = PyLong_AsUnsignedLongLong(args[1]); if (PyErr_Occurred()) { return NULL; } #else static PyObject* SdBus_request_name(SdBusObject* self, PyObject* args) { const char* service_name_char_ptr = NULL; unsigned long long flags_long_long = 0; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "sK", &service_name_char_ptr, &flags_long_long, NULL)); uint64_t flags = (uint64_t)flags_long_long; #endif int request_name_return_code = sd_bus_request_name(self->sd_bus_ref, service_name_char_ptr, flags); switch (request_name_return_code) { case -EEXIST: return PyErr_Format(exception_request_name_exists, "Name \"%s\" already owned.", service_name_char_ptr, NULL); break; case -EALREADY: return PyErr_Format(exception_request_name_already_owner, "Already own name \"%s\".", service_name_char_ptr, NULL); break; case 0: return PyErr_Format(exception_request_name_in_queue, "Queued up to acquire name \"%s\".", service_name_char_ptr, NULL); break; case 1: Py_RETURN_NONE; break; default: CALL_SD_BUS_AND_CHECK(request_name_return_code); break; } Py_UNREACHABLE(); } #ifndef Py_LIMITED_API static SdBusSlotObject* SdBus_add_object_manager(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* object_manager_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static SdBusSlotObject* SdBus_add_object_manager(SdBusObject* self, PyObject* args) { const char* object_manager_path = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &object_manager_path, NULL)); #endif SdBusSlotObject* new_slot_object CLEANUP_SD_BUS_SLOT = (SdBusSlotObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusSlot_class)); CALL_SD_BUS_AND_CHECK(sd_bus_add_object_manager(self->sd_bus_ref, &new_slot_object->slot_ref, object_manager_path)); Py_INCREF(new_slot_object); return new_slot_object; } #ifndef Py_LIMITED_API static PyObject* SdBus_emit_object_added(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* added_object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* SdBus_emit_object_added(SdBusObject* self, PyObject* args) { const char* added_object_path = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &added_object_path, NULL)); #endif CALL_SD_BUS_AND_CHECK(sd_bus_emit_object_added(self->sd_bus_ref, added_object_path)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBus_emit_object_removed(SdBusObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* removed_object_path = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* SdBus_emit_object_removed(SdBusObject* self, PyObject* args) { const char* removed_object_path = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &removed_object_path, NULL)); #endif CALL_SD_BUS_AND_CHECK(sd_bus_emit_object_removed(self->sd_bus_ref, removed_object_path)); Py_RETURN_NONE; } static PyObject* SdBus_close(SdBusObject* self, PyObject* Py_UNUSED(args)) { sd_bus_close(self->sd_bus_ref); if (NULL != self->loop && NULL != self->bus_fd) { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(self->loop, remove_reader_str, self->bus_fd, NULL))); Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(self->loop, remove_writer_str, self->bus_fd, NULL))); } if (NULL != self->timer_fd) { Py_XDECREF(PyObject_CallMethodObjArgs(self->loop, remove_reader_str, self->timer_fd, NULL)); // TODO: Close timerfd } Py_RETURN_NONE; } static PyObject* SdBus_start(SdBusObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_start(self->sd_bus_ref)); Py_RETURN_NONE; } static inline int sd_bus_get_events_zero_on_closed(SdBusObject* self) { int events = sd_bus_get_events(self->sd_bus_ref); if (-ENOTCONN == events) { return 0; } return events; }; static inline int sd_bus_get_timeout_uint_max_on_closed(SdBusObject* self, uint64_t* timeout_usec) { int r = sd_bus_get_timeout(self->sd_bus_ref, timeout_usec); if (-ENOTCONN == r) { *timeout_usec = UINT64_MAX; return 0; } return r; } static PyObject* SdBus_asyncio_update_fd_watchers(SdBusObject* self) { PyObject* running_loop = CALL_PYTHON_AND_CHECK(_get_or_bind_loop(self)); PyObject* drive_method CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyObject_GetAttrString((PyObject*)self, "process")); if (NULL == self->timer_fd) { self->timer_fd_int = CALL_SD_BUS_AND_CHECK(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)); if (self->timer_fd_int < 0) { PyErr_SetFromErrno(PyExc_OSError); } PyObject* timer_fd CLEANUP_PY_OBJECT = PyLong_FromLong((int)self->timer_fd_int); Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, add_reader_str, timer_fd, drive_method, NULL))); Py_INCREF(timer_fd); self->timer_fd = timer_fd; } uint64_t timeout_usec = UINT64_MAX; CALL_SD_BUS_AND_CHECK(sd_bus_get_timeout_uint_max_on_closed(self, &timeout_usec)); struct itimerspec bus_timer = {0}; if (timeout_usec == UINT64_MAX) { // Setting bus_timer to zero disarms timer. } else if (timeout_usec != 0) { bus_timer.it_value.tv_sec = timeout_usec / 1000000; bus_timer.it_value.tv_nsec = (timeout_usec % 1000000) * 1000; } else if (timeout_usec == 0) { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, call_soon_str, drive_method, NULL))); } CALL_SD_BUS_AND_CHECK(timerfd_settime(self->timer_fd_int, TFD_TIMER_ABSTIME, &bus_timer, NULL)); int events_to_watch = CALL_SD_BUS_AND_CHECK(sd_bus_get_events_zero_on_closed(self)); if (events_to_watch == self->asyncio_watchers_last_state) { // Do not update the watchers because state is the same Py_RETURN_NONE; } else { self->asyncio_watchers_last_state = events_to_watch; } if (NULL == self->bus_fd) { self->bus_fd = CALL_PYTHON_AND_CHECK(SdBus_get_fd(self, NULL)); } if (events_to_watch & POLLIN) { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, add_reader_str, self->bus_fd, drive_method, NULL))); } else { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, remove_reader_str, self->bus_fd, NULL))); } if (events_to_watch & POLLOUT) { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, add_writer_str, self->bus_fd, drive_method, NULL))); } else { Py_XDECREF(CALL_PYTHON_AND_CHECK(PyObject_CallMethodObjArgs(running_loop, remove_writer_str, self->bus_fd, NULL))); } Py_RETURN_NONE; } static PyMethodDef SdBus_methods[] = { {"call", (SD_BUS_PY_FUNC_TYPE)SdBus_call, SD_BUS_PY_METH, PyDoc_STR("Send message and block until the reply.")}, {"call_async", (SD_BUS_PY_FUNC_TYPE)SdBus_call_async, SD_BUS_PY_METH, PyDoc_STR("Async send message, returns awaitable future.")}, {"process", (PyCFunction)SdBus_process, METH_NOARGS, PyDoc_STR("Process pending IO work.")}, {"get_fd", (SD_BUS_PY_FUNC_TYPE)SdBus_get_fd, SD_BUS_PY_METH, PyDoc_STR("Get file descriptor to poll on.")}, {"new_method_call_message", (SD_BUS_PY_FUNC_TYPE)SdBus_new_method_call_message, SD_BUS_PY_METH, PyDoc_STR("Create new empty method call message.")}, {"new_property_get_message", (SD_BUS_PY_FUNC_TYPE)SdBus_new_property_get_message, SD_BUS_PY_METH, PyDoc_STR("Create new empty property get message.")}, {"new_property_set_message", (SD_BUS_PY_FUNC_TYPE)SdBus_new_property_set_message, SD_BUS_PY_METH, PyDoc_STR("Create new empty property set message.")}, {"new_signal_message", (SD_BUS_PY_FUNC_TYPE)SdBus_new_signal_message, SD_BUS_PY_METH, PyDoc_STR("Create new empty signal message.")}, {"add_interface", (SD_BUS_PY_FUNC_TYPE)SdBus_add_interface, SD_BUS_PY_METH, PyDoc_STR("Add interface to the bus.")}, {"match_signal_async", (SD_BUS_PY_FUNC_TYPE)SdBus_match_signal_async, SD_BUS_PY_METH, PyDoc_STR("Register signal callback asynchronously. Returns a Future that returns a SdBusSlot.")}, {"request_name_async", (SD_BUS_PY_FUNC_TYPE)SdBus_request_name_async, SD_BUS_PY_METH, PyDoc_STR("Request D-Bus name async.")}, {"request_name", (SD_BUS_PY_FUNC_TYPE)SdBus_request_name, SD_BUS_PY_METH, PyDoc_STR("Request D-Bus name blocking.")}, {"add_object_manager", (SD_BUS_PY_FUNC_TYPE)SdBus_add_object_manager, SD_BUS_PY_METH, PyDoc_STR("Add object manager at the path.")}, {"emit_object_added", (SD_BUS_PY_FUNC_TYPE)SdBus_emit_object_added, SD_BUS_PY_METH, PyDoc_STR("Emit signal that object was added.")}, {"emit_object_removed", (SD_BUS_PY_FUNC_TYPE)SdBus_emit_object_removed, SD_BUS_PY_METH, PyDoc_STR("Emit signal that object was removed.")}, {"close", (PyCFunction)SdBus_close, METH_NOARGS, PyDoc_STR("Close connection.")}, {"start", (PyCFunction)SdBus_start, METH_NOARGS, PyDoc_STR("Start connection.")}, {NULL, NULL, 0, NULL}, }; static PyObject* SdBus_address_getter(SdBusObject* self, void* Py_UNUSED(closure)) { const char* bus_address = NULL; int get_address_result = sd_bus_get_address(self->sd_bus_ref, &bus_address); if (-ENODATA == get_address_result) { // Bus has not been set yet Py_RETURN_NONE; } else { CALL_SD_BUS_AND_CHECK(get_address_result); } return PyUnicode_FromString(bus_address); } static PyObject* SdBus_method_call_timeout_usec_getter(SdBusObject* self, void* Py_UNUSED(closure)) { uint64_t timeout_usec = 0; CALL_SD_BUS_AND_CHECK(sd_bus_get_method_call_timeout(self->sd_bus_ref, &timeout_usec)); return PyLong_FromUnsignedLongLong((unsigned long long)timeout_usec); } static int SdBus_method_call_timeout_usec_setter(SdBusObject* self, PyObject* new_value, void* Py_UNUSED(closure)) { if (NULL == new_value) { PyErr_SetString(PyExc_ValueError, "Cannot delete method call timeout value"); return -1; } unsigned long long new_timeout_usec = PyLong_AsUnsignedLongLong(new_value); if ((((unsigned long long)-1) == new_timeout_usec) && (PyErr_Occurred() != NULL)) { return -1; } CALL_SD_BUS_CHECK_RETURN_NEG1(sd_bus_set_method_call_timeout(self->sd_bus_ref, (uint64_t)new_timeout_usec)); return 0; } static PyGetSetDef SdBus_properies[] = { {"address", (getter)SdBus_address_getter, NULL, PyDoc_STR("Bus address."), NULL}, {"method_call_timeout_usec", (getter)SdBus_method_call_timeout_usec_getter, (setter)SdBus_method_call_timeout_usec_setter, PyDoc_STR("D-Bus call timeout in microseconds."), NULL}, {0}, }; PyType_Spec SdBusType = { .name = "sd_bus_internals.SdBus", .basicsize = sizeof(SdBusObject), .itemsize = 0, .flags = Py_TPFLAGS_DEFAULT, .slots = (PyType_Slot[]){ {Py_tp_new, PyType_GenericNew}, {Py_tp_init, (initproc)SdBus_init}, {Py_tp_dealloc, (destructor)SdBus_dealloc}, {Py_tp_methods, SdBus_methods}, {Py_tp_getset, SdBus_properies}, {0, NULL}, }, }; python-sdbus-0.14.0/src/sdbus/sd_bus_internals_funcs.c000066400000000000000000000332741477456016000230650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sd_bus_internals.h" static SdBusObject* sd_bus_py_open(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored)) { SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open(&(new_sd_bus->sd_bus_ref))); return new_sd_bus; } static SdBusObject* sd_bus_py_open_user(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored)) { SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open_user(&(new_sd_bus->sd_bus_ref))); return new_sd_bus; } static SdBusObject* sd_bus_py_open_system(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored)) { SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open_system(&(new_sd_bus->sd_bus_ref))); return new_sd_bus; } static SdBusObject* sd_bus_py_open_system_remote(PyObject* Py_UNUSED(self), PyObject* args) { const char* remote_host_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &remote_host_char_ptr, NULL)); SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open_system_remote(&(new_sd_bus->sd_bus_ref), remote_host_char_ptr)); return new_sd_bus; } static SdBusObject* sd_bus_py_open_system_machine(PyObject* Py_UNUSED(self), PyObject* args) { const char* remote_host_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &remote_host_char_ptr, NULL)); SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open_system_machine(&(new_sd_bus->sd_bus_ref), remote_host_char_ptr)); return new_sd_bus; } static SdBusObject* sd_bus_py_open_user_machine(PyObject* Py_UNUSED(self), PyObject* args) { #ifndef LIBSYSTEMD_NO_OPEN_USER_MACHINE const char* remote_host_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &remote_host_char_ptr, NULL)); SdBusObject* new_sd_bus = (SdBusObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBus_class)); CALL_SD_BUS_AND_CHECK(sd_bus_open_user_machine(&(new_sd_bus->sd_bus_ref), remote_host_char_ptr)); return new_sd_bus; #else PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 248 does not support opening machine user bus"); return NULL; #endif } #ifndef Py_LIMITED_API static PyObject* encode_object_path(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); const char* prefix_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* external_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); #else static PyObject* encode_object_path(PyObject* Py_UNUSED(self), PyObject* args) { const char* prefix_char_ptr = NULL; const char* external_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ss", &prefix_char_ptr, &external_char_ptr, NULL)); #endif #ifdef LIBSYSTEMD_NO_VALIDATION_FUNCS PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 246 does not support validation functions"); return NULL; #else if (!sd_bus_object_path_is_valid(prefix_char_ptr)) { PyErr_SetString(PyExc_ValueError, "Prefix is not a valid object path"); return NULL; } const char* new_char_ptr CLEANUP_STR_MALLOC = NULL; CALL_SD_BUS_AND_CHECK(sd_bus_path_encode(prefix_char_ptr, external_char_ptr, (char**)(&new_char_ptr))); return PyUnicode_FromString(new_char_ptr); #endif } #ifndef Py_LIMITED_API static PyObject* decode_object_path(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); const char* prefix_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* full_path_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); #else static PyObject* decode_object_path(PyObject* Py_UNUSED(self), PyObject* args) { const char* prefix_char_ptr = NULL; const char* full_path_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ss", &prefix_char_ptr, &full_path_char_ptr, NULL)); #endif const char* new_char_ptr CLEANUP_STR_MALLOC = NULL; CALL_SD_BUS_AND_CHECK(sd_bus_path_decode(full_path_char_ptr, prefix_char_ptr, (char**)(&new_char_ptr))); if (new_char_ptr) { return PyUnicode_FromString(new_char_ptr); } else { return PyUnicode_FromString(""); } } #ifndef Py_LIMITED_API static PyObject* map_exception_to_dbus_error(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyExceptionClass_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); PyObject* exception = args[0]; PyObject* dbus_error_string = args[1]; #else static PyObject* map_exception_to_dbus_error(PyObject* Py_UNUSED(self), PyObject* args) { PyObject* exception = NULL; PyObject* dbus_error_string = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "O!O!", PyExc_BaseException->ob_type, &exception, &PyUnicode_Type, &dbus_error_string, NULL)); #endif if (CALL_PYTHON_INT_CHECK(PyDict_Contains(dbus_error_to_exception_dict, dbus_error_string)) > 0) { PyErr_Format(PyExc_ValueError, "D-Bus error %R is already mapped.", dbus_error_string); return NULL; } CALL_PYTHON_INT_CHECK(PyDict_SetItem(dbus_error_to_exception_dict, dbus_error_string, exception)); CALL_PYTHON_INT_CHECK(PyDict_SetItem(exception_to_dbus_error_dict, exception, dbus_error_string)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* add_exception_mapping(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); PyObject* exception = args[0]; #else static PyObject* add_exception_mapping(PyObject* Py_UNUSED(self), PyObject* args) { PyObject* exception = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "O", &exception, NULL)); #endif PyObject* dbus_error_string CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyObject_GetAttrString(exception, "dbus_error_name")); if (CALL_PYTHON_INT_CHECK(PyDict_Contains(dbus_error_to_exception_dict, dbus_error_string)) > 0) { PyErr_Format(PyExc_ValueError, "D-Bus error %R is already mapped.", dbus_error_string); return NULL; } if (CALL_PYTHON_INT_CHECK(PyDict_Contains(exception_to_dbus_error_dict, exception)) > 0) { PyErr_Format(PyExc_ValueError, "Exception %R is already mapped to dbus error.", exception); return NULL; } CALL_PYTHON_INT_CHECK(PyDict_SetItem(dbus_error_to_exception_dict, dbus_error_string, exception)); CALL_PYTHON_INT_CHECK(PyDict_SetItem(exception_to_dbus_error_dict, exception, dbus_error_string)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* is_interface_name_valid(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* string_to_check = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* is_interface_name_valid(PyObject* Py_UNUSED(self), PyObject* args) { const char* string_to_check = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &string_to_check, NULL)); #endif #ifdef LIBSYSTEMD_NO_VALIDATION_FUNCS PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 246 does not support validation functions"); return NULL; #else if (sd_bus_interface_name_is_valid(string_to_check)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } #endif } #ifndef Py_LIMITED_API static PyObject* is_service_name_valid(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* string_to_check = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* is_service_name_valid(PyObject* Py_UNUSED(self), PyObject* args) { const char* string_to_check = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &string_to_check, NULL)); #endif #ifdef LIBSYSTEMD_NO_VALIDATION_FUNCS PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 246 does not support validation functions"); return NULL; #else if (sd_bus_service_name_is_valid(string_to_check)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } #endif } #ifndef Py_LIMITED_API static PyObject* is_member_name_valid(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* string_to_check = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* is_member_name_valid(PyObject* Py_UNUSED(self), PyObject* args) { const char* string_to_check = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &string_to_check, NULL)); #endif #ifdef LIBSYSTEMD_NO_VALIDATION_FUNCS PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 246 does not support validation functions"); return NULL; #else if (sd_bus_member_name_is_valid(string_to_check)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } #endif } #ifndef Py_LIMITED_API static PyObject* is_object_path_valid(PyObject* Py_UNUSED(self), PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(1); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* string_to_check = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); #else static PyObject* is_object_path_valid(PyObject* Py_UNUSED(self), PyObject* args) { const char* string_to_check = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "s", &string_to_check, NULL)); #endif #ifdef LIBSYSTEMD_NO_VALIDATION_FUNCS PyErr_SetString(PyExc_NotImplementedError, "libsystemd < 246 does not support validation functions"); return NULL; #else if (sd_bus_object_path_is_valid(string_to_check)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } #endif } PyMethodDef SdBusPyInternal_methods[] = { {"sd_bus_open", (PyCFunction)sd_bus_py_open, METH_NOARGS, PyDoc_STR("Open dbus connection. Session bus running as user or system bus as daemon.")}, {"sd_bus_open_user", (PyCFunction)sd_bus_py_open_user, METH_NOARGS, PyDoc_STR("Open user session dbus.")}, {"sd_bus_open_system", (PyCFunction)sd_bus_py_open_system, METH_NOARGS, PyDoc_STR("Open system dbus.")}, {"sd_bus_open_system_remote", (PyCFunction)sd_bus_py_open_system_remote, METH_VARARGS, PyDoc_STR("Open remote system bus over SSH.")}, {"sd_bus_open_user_machine", (PyCFunction)sd_bus_py_open_user_machine, METH_VARARGS, PyDoc_STR("Open system bus in systemd-nspawn container.")}, {"sd_bus_open_system_machine", (PyCFunction)sd_bus_py_open_system_machine, METH_VARARGS, PyDoc_STR("Open user bus in systemd-nspawn container.")}, {"encode_object_path", (SD_BUS_PY_FUNC_TYPE)encode_object_path, SD_BUS_PY_METH, PyDoc_STR("Encode object path with object path prefix and arbitrary string.")}, {"decode_object_path", (SD_BUS_PY_FUNC_TYPE)decode_object_path, SD_BUS_PY_METH, PyDoc_STR("Decode object path with object path prefix and arbitrary string.")}, {"map_exception_to_dbus_error", (SD_BUS_PY_FUNC_TYPE)map_exception_to_dbus_error, SD_BUS_PY_METH, PyDoc_STR("Map exception to a D-Bus error name.")}, {"add_exception_mapping", (SD_BUS_PY_FUNC_TYPE)add_exception_mapping, SD_BUS_PY_METH, PyDoc_STR("Add exception to the mapping of dbus error names.")}, {"is_interface_name_valid", (SD_BUS_PY_FUNC_TYPE)is_interface_name_valid, SD_BUS_PY_METH, PyDoc_STR("Is the string valid interface name?")}, {"is_service_name_valid", (SD_BUS_PY_FUNC_TYPE)is_service_name_valid, SD_BUS_PY_METH, PyDoc_STR("Is the string valid service name?")}, {"is_member_name_valid", (SD_BUS_PY_FUNC_TYPE)is_member_name_valid, SD_BUS_PY_METH, PyDoc_STR("Is the string valid member name?")}, {"is_object_path_valid", (SD_BUS_PY_FUNC_TYPE)is_object_path_valid, SD_BUS_PY_METH, PyDoc_STR("Is the string valid object path?")}, {NULL, NULL, 0, NULL}, }; python-sdbus-0.14.0/src/sdbus/sd_bus_internals_interface.c000066400000000000000000000574361477456016000237150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sd_bus_internals.h" // TODO: adding interface to different buses, recalculating vtable static int SdBusInterface_init(SdBusInterfaceObject* self, PyObject* Py_UNUSED(args), PyObject* Py_UNUSED(kwds)) { self->interface_slot = (SdBusSlotObject*)CALL_PYTHON_CHECK_RETURN_NEG1(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusSlot_class)); self->method_list = CALL_PYTHON_CHECK_RETURN_NEG1(PyList_New((Py_ssize_t)0)); self->method_dict = CALL_PYTHON_CHECK_RETURN_NEG1(PyDict_New()); self->property_list = CALL_PYTHON_CHECK_RETURN_NEG1(PyList_New((Py_ssize_t)0)); self->property_get_dict = CALL_PYTHON_CHECK_RETURN_NEG1(PyDict_New()); self->property_set_dict = CALL_PYTHON_CHECK_RETURN_NEG1(PyDict_New()); self->signal_list = CALL_PYTHON_CHECK_RETURN_NEG1(PyList_New((Py_ssize_t)0)); self->vtable = NULL; return 0; } static void SdBusInterface_dealloc(SdBusInterfaceObject* self) { Py_XDECREF(self->interface_slot); Py_XDECREF(self->method_list); Py_XDECREF(self->method_dict); Py_XDECREF(self->property_list); Py_XDECREF(self->property_get_dict); Py_XDECREF(self->property_set_dict); Py_XDECREF(self->signal_list); if (self->vtable) { free(self->vtable); } SD_BUS_DEALLOC_TAIL; } static inline int _check_callable_or_none(PyObject* some_object) { return PyCallable_Check(some_object) || (Py_None == some_object); } #ifndef Py_LIMITED_API static PyObject* SdBusInterface_add_property(SdBusInterfaceObject* self, PyObject* const* args, Py_ssize_t nargs) { // Arguments // Name, Signature, Get, Set, Flags SD_BUS_PY_CHECK_ARGS_NUMBER(5); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PyCallable_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, _check_callable_or_none); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(4, PyLong_Check); PyObject* name = args[0]; PyObject* signature = args[1]; PyObject* getter = args[2]; PyObject* setter = args[3]; PyObject* flags = args[4]; #else static PyObject* SdBusInterface_add_property(SdBusInterfaceObject* self, PyObject* args) { PyObject* name = NULL; PyObject* signature = NULL; PyObject* getter = NULL; PyObject* setter = NULL; PyObject* flags = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "OOOOO", &name, &signature, &getter, &setter, &flags, NULL)); #endif PyObject* name_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(name); PyObject* signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(signature); PyObject* new_tuple CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyTuple_Pack(4, name_bytes, signature_bytes, flags, setter)); CALL_PYTHON_INT_CHECK(PyList_Append(self->property_list, new_tuple)); CALL_PYTHON_INT_CHECK(PyDict_SetItem(self->property_get_dict, name_bytes, getter)); CALL_PYTHON_INT_CHECK(PyDict_SetItem(self->property_set_dict, name_bytes, setter)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBusInterface_add_method(SdBusInterfaceObject* self, PyObject* const* args, Py_ssize_t nargs) { // Arguments // Method name, signature, names of input values, result signature, // names of result values, flags, callback function or coroutine SD_BUS_PY_CHECK_ARGS_NUMBER(7); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PySequence_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(4, PySequence_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(5, PyLong_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(6, PyCallable_Check); PyObject* method_name = args[0]; PyObject* input_signature = args[1]; PyObject* input_names = args[2]; PyObject* result_signature = args[3]; PyObject* result_names = args[4]; PyObject* flags = args[5]; PyObject* callback_func = args[6]; #else static PyObject* SdBusInterface_add_method(SdBusInterfaceObject* self, PyObject* args) { PyObject* method_name = NULL; PyObject* input_signature = NULL; PyObject* input_names = NULL; PyObject* result_signature = NULL; PyObject* result_names = NULL; PyObject* flags = NULL; PyObject* callback_func = NULL; CALL_PYTHON_BOOL_CHECK( PyArg_ParseTuple(args, "OOOOOOO", &method_name, &input_signature, &input_names, &result_signature, &result_names, &flags, &callback_func, NULL)); #endif PyObject* method_name_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(method_name); PyObject* input_signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(input_signature); PyObject* result_signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(result_signature); PyObject* argument_name_list CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyList_New(0)); CALL_PYTHON_EXPECT_NONE(PyObject_CallMethodObjArgs(argument_name_list, extend_str, input_names, NULL)); CALL_PYTHON_EXPECT_NONE(PyObject_CallMethodObjArgs(argument_name_list, extend_str, result_names, NULL)); // HACK: add a null separator to the end of the array CALL_PYTHON_EXPECT_NONE(PyObject_CallMethodObjArgs(argument_name_list, append_str, null_str, NULL)); PyObject* argument_names_string CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_Join(null_str, argument_name_list)); PyObject* argument_names_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(argument_names_string); // Method name, input signature, return signature, arguments names, // flags PyObject* new_tuple CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyTuple_Pack(5, method_name_bytes, input_signature_bytes, result_signature_bytes, argument_names_bytes, flags)); CALL_PYTHON_INT_CHECK(PyList_Append(self->method_list, new_tuple)); CALL_PYTHON_INT_CHECK(PyDict_SetItem(self->method_dict, method_name_bytes, callback_func)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBusInterface_add_signal(SdBusInterfaceObject* self, PyObject* const* args, Py_ssize_t nargs) { // Arguments // Signal name, signature, names of input values, flags SD_BUS_PY_CHECK_ARGS_NUMBER(4); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(2, PySequence_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(3, PyLong_Check); PyObject* signal_name = args[0]; PyObject* signature = args[1]; PyObject* input_names = args[2]; PyObject* flags = args[3]; #else static PyObject* SdBusInterface_add_signal(SdBusInterfaceObject* self, PyObject* args) { PyObject* signal_name = NULL; PyObject* signature = NULL; PyObject* input_names = NULL; PyObject* flags = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "OOOO", &signal_name, &signature, &input_names, &flags, NULL)); #endif PyObject* signal_name_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(signal_name); PyObject* signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(signature); PyObject* argument_name_list CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyList_New(0)); CALL_PYTHON_EXPECT_NONE(PyObject_CallMethodObjArgs(argument_name_list, extend_str, input_names, NULL)); // HACK: add a null separator to the end of the array CALL_PYTHON_EXPECT_NONE(PyObject_CallMethodObjArgs(argument_name_list, append_str, null_str, NULL)); PyObject* argument_names_string CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_Join(null_str, argument_name_list)); PyObject* argument_names_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(argument_names_string); // Signal name, signature, names of input values, flags PyObject* new_tuple CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyTuple_Pack(4, signal_name_bytes, signature_bytes, argument_names_bytes, flags)); CALL_PYTHON_INT_CHECK(PyList_Append(self->signal_list, new_tuple)); Py_RETURN_NONE; } static int _SdBusInterface_callback(sd_bus_message* m, void* userdata, sd_bus_error* ret_error); static int _SdBusInterface_property_get_callback(sd_bus* bus, const char* path, const char* interface, const char* property, sd_bus_message* reply, void* userdata, sd_bus_error* ret_error); static int _SdBusInterface_property_set_callback(sd_bus* bus, const char* path, const char* interface, const char* property, sd_bus_message* value, void* userdata, sd_bus_error* ret_error); static PyObject* SdBusInterface_create_vtable(SdBusInterfaceObject* self, PyObject* const* Py_UNUSED(args)) { if (self->vtable) { Py_RETURN_NONE; } Py_ssize_t num_of_methods = PyList_Size(self->method_list); Py_ssize_t num_of_properties = PyList_Size(self->property_list); Py_ssize_t num_of_signals = PyList_Size(self->signal_list); self->vtable = calloc(num_of_signals + num_of_properties + num_of_methods + 2, sizeof(sd_bus_vtable)); if (self->vtable == NULL) { return PyErr_NoMemory(); } sd_bus_vtable start_vtable = SD_BUS_VTABLE_START(0); self->vtable[0] = start_vtable; Py_ssize_t current_index = 1; // Iter method definitions for (Py_ssize_t i = 0; i < num_of_methods; ( { ++i; ++current_index; })) { PyObject* method_tuple = CALL_PYTHON_AND_CHECK(PyList_GetItem(self->method_list, i)); PyObject* method_name_object = CALL_PYTHON_AND_CHECK(PyTuple_GetItem(method_tuple, 0)); PyObject* input_signature_object = CALL_PYTHON_AND_CHECK(PyTuple_GetItem(method_tuple, 1)); PyObject* result_signature_object = CALL_PYTHON_AND_CHECK(PyTuple_GetItem(method_tuple, 2)); PyObject* argument_names_string = CALL_PYTHON_AND_CHECK(PyTuple_GetItem(method_tuple, 3)); const char* method_name_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(method_name_object); const char* input_signature_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(input_signature_object); const char* result_signature_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(result_signature_object); const char* argument_names_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(argument_names_string); PyObject* flags_object = CALL_PYTHON_AND_CHECK(PyTuple_GetItem(method_tuple, 4)); unsigned long long flags_long = PyLong_AsUnsignedLongLong(flags_object); if (PyErr_Occurred()) { return NULL; } sd_bus_vtable temp_vtable = SD_BUS_METHOD_WITH_NAMES_OFFSET(method_name_char_ptr, input_signature_char_ptr, argument_names_char_ptr, result_signature_char_ptr, , _SdBusInterface_callback, 0, flags_long); self->vtable[current_index] = temp_vtable; } for (Py_ssize_t i = 0; i < num_of_properties; ( { ++i; ++current_index; })) { PyObject* property_tuple = SD_BUS_PY_LIST_GET_ITEM(self->property_list, i); PyObject* property_name_str = SD_BUS_PY_TUPLE_GET_ITEM(property_tuple, 0); PyObject* property_signature_str = SD_BUS_PY_TUPLE_GET_ITEM(property_tuple, 1); PyObject* property_flags = SD_BUS_PY_TUPLE_GET_ITEM(property_tuple, 2); PyObject* setter_or_none = SD_BUS_PY_TUPLE_GET_ITEM(property_tuple, 3); const char* property_name_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(property_name_str); const char* property_signature_const_char = SD_BUS_PY_BYTES_AS_CHAR_PTR(property_signature_str); unsigned long long flags_long = PyLong_AsUnsignedLongLong(property_flags); if (PyErr_Occurred()) { return NULL; } if (setter_or_none == Py_None) { sd_bus_vtable temp_vtable = SD_BUS_PROPERTY(property_name_char_ptr, // Name property_signature_const_char, // Signature _SdBusInterface_property_get_callback, // Get 0, // Offset flags_long // Flags ); self->vtable[current_index] = temp_vtable; } else { sd_bus_vtable temp_vtable = SD_BUS_WRITABLE_PROPERTY(property_name_char_ptr, // Name property_signature_const_char, // Signature _SdBusInterface_property_get_callback, // Get _SdBusInterface_property_set_callback, // Set 0, // Offset flags_long // Flags ); self->vtable[current_index] = temp_vtable; } } for (Py_ssize_t i = 0; i < num_of_signals; ( { ++i; ++current_index; })) { PyObject* signal_tuple = SD_BUS_PY_LIST_GET_ITEM(self->signal_list, i); PyObject* signal_name_str = SD_BUS_PY_TUPLE_GET_ITEM(signal_tuple, 0); PyObject* signal_signature_str = SD_BUS_PY_TUPLE_GET_ITEM(signal_tuple, 1); PyObject* signal_input_names = SD_BUS_PY_TUPLE_GET_ITEM(signal_tuple, 2); PyObject* signal_flags = SD_BUS_PY_TUPLE_GET_ITEM(signal_tuple, 3); const char* signal_name_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(signal_name_str); const char* signal_signature_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(signal_signature_str); const char* signal_args_names_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(signal_input_names); unsigned long long flags_long = PyLong_AsUnsignedLongLong(signal_flags); if (PyErr_Occurred()) { return NULL; } sd_bus_vtable temp_vtable = SD_BUS_SIGNAL_WITH_NAMES(signal_name_char_ptr, signal_signature_char_ptr, signal_args_names_char_ptr, flags_long); self->vtable[current_index] = temp_vtable; } sd_bus_vtable end_vtable = SD_BUS_VTABLE_END; self->vtable[current_index] = end_vtable; Py_RETURN_NONE; } static PyMethodDef SdBusInterface_methods[] = { {"add_method", (SD_BUS_PY_FUNC_TYPE)SdBusInterface_add_method, SD_BUS_PY_METH, PyDoc_STR("Add method to the D-Bus interface.")}, {"add_property", (SD_BUS_PY_FUNC_TYPE)SdBusInterface_add_property, SD_BUS_PY_METH, PyDoc_STR("Add property to the D-Bus interface.")}, {"add_signal", (SD_BUS_PY_FUNC_TYPE)SdBusInterface_add_signal, SD_BUS_PY_METH, PyDoc_STR("Add signal to the D-Bus interface.")}, {"_create_vtable", (PyCFunction)SdBusInterface_create_vtable, METH_NOARGS, PyDoc_STR("Creates the vtable.")}, {NULL, NULL, 0, NULL}, }; static PyMemberDef SdBusInterface_members[] = {{"slot", T_OBJECT, offsetof(SdBusInterfaceObject, interface_slot), READONLY, NULL}, {"method_list", T_OBJECT, offsetof(SdBusInterfaceObject, method_list), READONLY, NULL}, {"method_dict", T_OBJECT, offsetof(SdBusInterfaceObject, method_dict), READONLY, NULL}, {"property_list", T_OBJECT, offsetof(SdBusInterfaceObject, property_list), READONLY, NULL}, {"property_get_dict", T_OBJECT, offsetof(SdBusInterfaceObject, property_get_dict), READONLY, NULL}, {"property_set_dict", T_OBJECT, offsetof(SdBusInterfaceObject, property_set_dict), READONLY, NULL}, {"signal_list", T_OBJECT, offsetof(SdBusInterfaceObject, signal_list), READONLY, NULL}, {0}}; PyType_Spec SdBusInterfaceType = { .name = "sd_bus_internals.SdBusInterface", .basicsize = sizeof(SdBusInterfaceObject), .itemsize = 0, .flags = Py_TPFLAGS_DEFAULT, .slots = (PyType_Slot[]){ {Py_tp_new, PyType_GenericNew}, {Py_tp_init, (initproc)SdBusInterface_init}, {Py_tp_dealloc, (destructor)SdBusInterface_dealloc}, {Py_tp_methods, SdBusInterface_methods}, {Py_tp_members, SdBusInterface_members}, {0, NULL}, }, }; static int set_dbus_error_from_python_exception(sd_bus_error* ret_error) { #ifdef Py_LIMITED_API PyObject* dbus_error_bytes CLEANUP_PY_OBJECT = NULL; #endif PyObject* current_exception = PyErr_Occurred(); if (NULL == current_exception) { goto fail; } PyObject* dbus_error_str = CALL_PYTHON_GOTO_FAIL(PyDict_GetItem(exception_to_dbus_error_dict, current_exception)); #ifndef Py_LIMITED_API const char* dbus_error_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR_GOTO_FAIL(dbus_error_str); #else dbus_error_bytes = SD_BUS_PY_UNICODE_AS_BYTES_GOTO_FAIL(dbus_error_str); const char* dbus_error_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR_GOTO_FAIL(dbus_error_bytes); #endif return sd_bus_error_set(ret_error, dbus_error_char_ptr, ""); fail: return sd_bus_error_set(ret_error, SD_BUS_ERROR_FAILED, ""); } #define METHOD_CALLBACK_ERROR_CHECK(py_function) CALL_PYTHON_FAIL_ACTION(py_function, return set_dbus_error_from_python_exception(ret_error)) static int _SdBusInterface_callback(sd_bus_message* m, void* userdata, sd_bus_error* ret_error) { // TODO: Better error handling SdBusInterfaceObject* self = userdata; // Get the member name from the message const char* member_char_ptr = sd_bus_message_get_member(m); PyObject* member_name_bytes CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(PyBytes_FromString(member_char_ptr)); PyObject* callback_object = METHOD_CALLBACK_ERROR_CHECK(PyDict_GetItem(self->method_dict, member_name_bytes)); PyObject* running_loop CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(asyncio_get_running_loop, NULL)); PyObject* new_message CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); _SdBusMessage_set_messsage((SdBusMessageObject*)new_message, m); PyObject* is_coroutine_test_object CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(is_coroutine_function, callback_object, NULL)); if (Py_True == is_coroutine_test_object) { // Create coroutine PyObject* coroutine_activated CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(callback_object, new_message, NULL)); Py_XDECREF(METHOD_CALLBACK_ERROR_CHECK(PyObject_CallMethodObjArgs(running_loop, create_task_str, coroutine_activated, NULL))); } else { Py_XDECREF(METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(callback_object, new_message, NULL))); } sd_bus_error_set(ret_error, NULL, NULL); return 1; } static int _SdBusInterface_property_get_callback(sd_bus* Py_UNUSED(bus), const char* Py_UNUSED(path), const char* Py_UNUSED(interface), const char* property, sd_bus_message* reply, void* userdata, sd_bus_error* ret_error) { SdBusInterfaceObject* self = userdata; PyObject* property_name_bytes CLEANUP_PY_OBJECT = NULL; PyObject* get_call = NULL; PyObject* new_message CLEANUP_PY_OBJECT = NULL; property_name_bytes = METHOD_CALLBACK_ERROR_CHECK(PyBytes_FromString(property)); get_call = METHOD_CALLBACK_ERROR_CHECK(PyDict_GetItem(self->property_get_dict, property_name_bytes)); new_message = METHOD_CALLBACK_ERROR_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); _SdBusMessage_set_messsage((SdBusMessageObject*)new_message, reply); Py_XDECREF(METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(get_call, new_message, NULL))); return 0; } static int _SdBusInterface_property_set_callback(sd_bus* Py_UNUSED(bus), const char* Py_UNUSED(path), const char* Py_UNUSED(interface), const char* property, sd_bus_message* value, void* userdata, sd_bus_error* ret_error) { SdBusInterfaceObject* self = userdata; PyObject* property_name_bytes CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(PyBytes_FromString(property)); PyObject* set_call = METHOD_CALLBACK_ERROR_CHECK(PyDict_GetItem(self->property_set_dict, property_name_bytes)); PyObject* new_message CLEANUP_PY_OBJECT = METHOD_CALLBACK_ERROR_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); _SdBusMessage_set_messsage((SdBusMessageObject*)new_message, value); Py_XDECREF(METHOD_CALLBACK_ERROR_CHECK(PyObject_CallFunctionObjArgs(set_call, new_message, NULL))); return 0; } python-sdbus-0.14.0/src/sdbus/sd_bus_internals_message.c000066400000000000000000001426631477456016000233760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* Copyright (C) 2020, 2021 igo95862 This file is part of python-sdbus 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sd_bus_internals.h" void _SdBusMessage_set_messsage(SdBusMessageObject* self, sd_bus_message* new_message) { self->message_ref = sd_bus_message_ref(new_message); } static void SdBusMessage_dealloc(SdBusMessageObject* self) { sd_bus_message_unref(self->message_ref); SD_BUS_DEALLOC_TAIL; } static PyObject* SdBusMessage_seal(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_message_seal(self->message_ref, 0, 0)); Py_RETURN_NONE; } static PyObject* SdBusMessage_dump(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_message_dump(self->message_ref, 0, SD_BUS_MESSAGE_DUMP_WITH_HEADER)); CALL_SD_BUS_AND_CHECK(sd_bus_message_rewind(self->message_ref, 1)); Py_RETURN_NONE; } typedef struct { sd_bus_message* message; const char* container_char_ptr; size_t index; size_t max_index; } _Parse_state; #define _CHECK_PARSER_NOT_NULL(parser) \ if (parser_state->container_char_ptr[parser_state->index] == '\0') { \ PyErr_SetString(PyExc_TypeError, "Data signature too short"); \ return NULL; \ } static PyObject* _parse_complete(PyObject* complete_obj, _Parse_state* parser_state); static PyObject* _parse_basic(PyObject* basic_obj, _Parse_state* parser_state) { char basic_type = parser_state->container_char_ptr[parser_state->index]; switch (basic_type) { // Unsigned case 'y': { unsigned long long the_ulong_long = PyLong_AsUnsignedLongLong(basic_obj); PYTHON_ERR_OCCURED; if (UINT8_MAX < the_ulong_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'y' type, overflow. 'y' " "is max %llu", (unsigned long long)UINT8_MAX); return NULL; } uint8_t byte_to_add = (uint8_t)the_ulong_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &byte_to_add)); break; } case 'q': { unsigned long long the_ulong_long = PyLong_AsUnsignedLongLong(basic_obj); PYTHON_ERR_OCCURED; if (UINT16_MAX < the_ulong_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'q' type, overflow. 'q' " "is max %llu", (unsigned long long)UINT16_MAX); return NULL; } uint16_t q_to_add = (uint16_t)the_ulong_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &q_to_add)); break; } case 'u': { unsigned long long the_ulong_long = PyLong_AsUnsignedLongLong(basic_obj); PYTHON_ERR_OCCURED; if (UINT32_MAX < the_ulong_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'u' type, overflow. 'u' " "is max %lu", (unsigned long)UINT32_MAX); return NULL; } uint32_t u_to_add = (uint32_t)the_ulong_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &u_to_add)); break; } case 't': { unsigned long long the_ulong_long = PyLong_AsUnsignedLongLong(basic_obj); PYTHON_ERR_OCCURED; uint64_t t_to_add = the_ulong_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &t_to_add)); break; } // Signed case 'n': { long long the_long_long = PyLong_AsLongLong(basic_obj); PYTHON_ERR_OCCURED; if (INT16_MAX < the_long_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'n' type, overflow. 'n' " "is max %lli", (long long)INT16_MAX); return NULL; } if (INT16_MIN > the_long_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'n' type, underflow. 'n' " "is min %lli", (long long)INT16_MIN); return NULL; } int16_t n_to_add = (int16_t)the_long_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &n_to_add)); break; } case 'i': { long long the_long_long = PyLong_AsLongLong(basic_obj); PYTHON_ERR_OCCURED; if (INT32_MAX < the_long_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'i' type, overflow. 'i' " "is max %lli", (long long)INT32_MAX); return NULL; } if (INT32_MIN > the_long_long) { PyErr_Format(PyExc_OverflowError, "Cannot convert int to " "'i' type, underflow. 'i' " "is min %lli", (long long)INT32_MIN); return NULL; } int32_t i_to_add = (int32_t)the_long_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &i_to_add)); break; } case 'x': { long long the_long_long = PyLong_AsLongLong(basic_obj); PYTHON_ERR_OCCURED; int64_t x_to_add = the_long_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &x_to_add)); break; } case 'h': { long long the_long_long = PyLong_AsLongLong(basic_obj); PYTHON_ERR_OCCURED; int h_to_add = (int)the_long_long; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &h_to_add)); break; } case 'b': { if (!PyBool_Check(basic_obj)) { PyErr_Format(PyExc_TypeError, "Message append error, " "expected bool got %R", basic_obj); return NULL; } int bool_to_add = (basic_obj == Py_True); CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &bool_to_add)); break; } case 'd': { if (!PyFloat_Check(basic_obj)) { PyErr_Format(PyExc_TypeError, "Message append error, " "expected double got %R", basic_obj); return NULL; } double double_to_add = PyFloat_AsDouble(basic_obj); PYTHON_ERR_OCCURED; CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, &double_to_add)); break; } case 'o': case 'g': case 's': { if (!PyUnicode_Check(basic_obj)) { PyErr_Format(PyExc_TypeError, "Message append error, " "expected str got %R", basic_obj); return NULL; } #ifndef Py_LIMITED_API const char* char_ptr_to_append = SD_BUS_PY_UNICODE_AS_CHAR_PTR(basic_obj); #else PyObject* bytes_to_append CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(basic_obj); const char* char_ptr_to_append = SD_BUS_PY_BYTES_AS_CHAR_PTR(bytes_to_append); #endif CALL_SD_BUS_AND_CHECK(sd_bus_message_append_basic(parser_state->message, basic_type, char_ptr_to_append)); break; } default: PyErr_Format(PyExc_ValueError, "Unknown message append type: %c", (int)basic_type); return NULL; break; } parser_state->index++; Py_RETURN_NONE; } static size_t _find_struct_end(const char* container_char_ptr, size_t current_index) { // Initial state // "...(...)..." // ^ int round_bracket_count = 1; for (; container_char_ptr[current_index] != '\0'; ++current_index) { char current_char = container_char_ptr[current_index]; if (current_char == ')') { --round_bracket_count; } if (current_char == '(') { ++round_bracket_count; } if (round_bracket_count == 0) { return current_index; } if (round_bracket_count < 0) { PyErr_SetString(PyExc_TypeError, "Round braces count <0. Check " "your signature."); return 0; } } PyErr_SetString(PyExc_TypeError, "Reached the end of signature before the struct end"); return 0; } static size_t _find_dict_end(const char* container_char_ptr, size_t current_index) { // Initial state // "...a{..}..." // ^ int curly_bracket_count = 0; for (; container_char_ptr[current_index] != '\0'; ++current_index) { char current_char = container_char_ptr[current_index]; if (current_char == '}') { --curly_bracket_count; } if (current_char == '{') { ++curly_bracket_count; } if (curly_bracket_count == 0) { // "...a{..}..." // ^ return current_index; } if (curly_bracket_count < 0) { PyErr_SetString(PyExc_TypeError, "Curly braces count <0. Check " "your signature."); return 0; } } PyErr_SetString(PyExc_TypeError, "Reached the end of signature before the struct end"); return 0; } static size_t _find_array_end(const char* container_char_ptr, size_t current_index) { // Initial state // "...as..." // ^ // "...a{sx}.." // ^ // "...a(as)..." // ^ while (container_char_ptr[current_index] == 'a') { current_index++; } char current_char = container_char_ptr[current_index]; // "...as..." // ^ // "...a{sx}.." // ^ // "...a(as)..." // ^ if (current_char == '\0') { PyErr_SetString(PyExc_TypeError, "Reached the end of signature before " "the array end"); return 0; } if (current_char == '{') { // "...a{sx}.." // ^ return _find_dict_end(container_char_ptr, current_index); } if (current_char == '(') { current_index++; // "...a(as)..." // ^ return _find_struct_end(container_char_ptr, current_index); } return current_index; } static const char* _subscript_char_ptr(const char* old_char_ptr, size_t start, size_t end) { // "abc(def)..." // 01234 | // 0123456 // 6 - 4 = 2 // Actual string // 'def\0' // 3 string length without \0 // 4 string length with \0 size_t new_string_size = (end - start) + 1; char* new_string = malloc(new_string_size + 1); if (new_string == NULL) { return NULL; } memcpy(new_string, old_char_ptr + start, new_string_size); // Set last byte to NUL new_string[new_string_size] = '\0'; return new_string; } static PyObject* _parse_dict(PyObject* dict_object, _Parse_state* parser_state) { // parser_state->container_char_ptr // "{sx}" // ^ if (!PyDict_Check(dict_object)) { PyErr_Format(PyExc_TypeError, "Message append error, expected dict got %R", dict_object); return NULL; } const char* dict_sig_char_ptr CLEANUP_STR_MALLOC = _subscript_char_ptr(parser_state->container_char_ptr, 1, parser_state->max_index - 2); // "sx" parser_state->container_char_ptr = dict_sig_char_ptr; // This is OK because its cleanup from // outside parser_state->max_index = strlen(dict_sig_char_ptr); PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(dict_object, &pos, &key, &value)) { CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(parser_state->message, 'e', dict_sig_char_ptr)); parser_state->index = 0; CALL_PYTHON_EXPECT_NONE(_parse_basic(key, parser_state)); CALL_PYTHON_EXPECT_NONE(_parse_complete(value, parser_state)); CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(parser_state->message)); } Py_RETURN_NONE; } static PyObject* _parse_array(PyObject* array_object, _Parse_state* parser_state) { // Initial state // "...as..." // ^ // "...a{sx}.." // ^ // "...a(as)..." // ^ size_t array_end = _find_array_end(parser_state->container_char_ptr, parser_state->index); if (array_end == 0) { return NULL; } // Array end points to // "...as..." // ^ // "...a{sx}.." // ^ // "...a(as)..." // ^ const char* array_sig_char_ptr CLEANUP_STR_MALLOC = _subscript_char_ptr(parser_state->container_char_ptr, parser_state->index + 1, array_end); // array_sig_char_ptr // "...as..." // "s" // "...a{sx}.." // "{sx}" // "...a(as)..." // "(as)" _Parse_state array_parser = { .message = parser_state->message, .container_char_ptr = array_sig_char_ptr, .index = 0, .max_index = strlen(array_sig_char_ptr), }; if (array_parser.container_char_ptr[0] == '{') { CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(parser_state->message, 'a', array_sig_char_ptr)); CALL_PYTHON_EXPECT_NONE(_parse_dict(array_object, &array_parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(parser_state->message)); } else if (array_parser.container_char_ptr[0] == 'y') { char* char_ptr_to_add = NULL; ssize_t size_of_array = 0; if (PyByteArray_Check(array_object)) { char_ptr_to_add = PyByteArray_AsString(array_object); if (char_ptr_to_add == NULL) { return NULL; } size_of_array = PyByteArray_Size(array_object); if (size_of_array == -1) { return NULL; } } else if (PyBytes_Check(array_object)) { char_ptr_to_add = PyBytes_AsString(array_object); if (char_ptr_to_add == NULL) { return NULL; } size_of_array = PyBytes_Size(array_object); if (size_of_array == -1) { return NULL; } } else { PyErr_Format(PyExc_TypeError, "Expected bytes or byte " "array, got %R", array_object); return NULL; } CALL_SD_BUS_AND_CHECK(sd_bus_message_append_array(parser_state->message, 'y', char_ptr_to_add, (size_t)size_of_array)); } else { if (!PyList_Check(array_object)) { PyErr_Format(PyExc_TypeError, "Message append error, " "expected array got %R", array_object); return NULL; } // "...as..." // "s" // "...aa{sx}.." // "a{sx}" // "...a(as)..." // "(as)" CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(parser_state->message, 'a', array_sig_char_ptr)); for (Py_ssize_t i = 0; i < SD_BUS_PY_LIST_GET_SIZE(array_object); ++i) { CALL_PYTHON_EXPECT_NONE(_parse_complete(SD_BUS_PY_LIST_GET_ITEM(array_object, i), &array_parser)); array_parser.index = 0; } CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(parser_state->message)); } parser_state->index = array_end + 1; // index points to // "...as..." // ^ // "...a{sx}.." // ^ // "...a(as)..." // ^ Py_RETURN_NONE; } static PyObject* _parse_struct(PyObject* tuple_object, _Parse_state* parser_state) { // Initial state // "...(...)..." // ^ if (!PyTuple_Check(tuple_object)) { PyErr_Format(PyExc_TypeError, "Message append error, expected tuple got %R", tuple_object); return NULL; } parser_state->index++; // "...(...)..." // ^ size_t struct_end = _find_struct_end(parser_state->container_char_ptr, parser_state->index); if (struct_end == 0) { return NULL; } // Struct end points to // "...(...)..." // ^ const char* struct_signature CLEANUP_STR_MALLOC = _subscript_char_ptr(parser_state->container_char_ptr, parser_state->index, struct_end - 1); // struct_signature should be // "...(...)..." // ^ ^ // "..." CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(parser_state->message, 'r', struct_signature)); for (Py_ssize_t i = 0; i < SD_BUS_PY_TUPLE_GET_SIZE(tuple_object); ++i) { // Use original parser as there is not much reason to // create new one CALL_PYTHON_EXPECT_NONE(_parse_complete(SD_BUS_PY_TUPLE_GET_ITEM(tuple_object, i), parser_state)); } CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(parser_state->message)); // "...(...)..." // ^ parser_state->index++; // Final state // "...(...)..." // ^ Py_RETURN_NONE; } static PyObject* _parse_variant(PyObject* tuple_object, _Parse_state* parser_state) { // Initial state "...v..." // ^ if (!PyTuple_Check(tuple_object)) { PyErr_Format(PyExc_TypeError, "Message append error, expected tuple got %R", tuple_object); return NULL; } if (SD_BUS_PY_TUPLE_GET_SIZE(tuple_object) != 2) { PyErr_Format(PyExc_TypeError, "Expected tuple of only 2 elements got %zi", SD_BUS_PY_TUPLE_GET_SIZE(tuple_object)); return NULL; } PyObject* variant_signature = SD_BUS_PY_TUPLE_GET_ITEM(tuple_object, 0); #ifndef Py_LIMITED_API const char* variant_signature_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(variant_signature); #else PyObject* variant_signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(variant_signature); const char* variant_signature_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(variant_signature_bytes); #endif _Parse_state variant_parser = { .message = parser_state->message, .max_index = strlen(variant_signature_char_ptr), .container_char_ptr = variant_signature_char_ptr, .index = 0, }; CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(parser_state->message, 'v', variant_signature_char_ptr)); PyObject* variant_body = SD_BUS_PY_TUPLE_GET_ITEM(tuple_object, 1); CALL_PYTHON_EXPECT_NONE(_parse_complete(variant_body, &variant_parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(parser_state->message)); // Final state "...v..." // ^ parser_state->index++; Py_RETURN_NONE; } static PyObject* _parse_complete(PyObject* complete_obj, _Parse_state* parser_state) { // Initial state "..." // ^ _CHECK_PARSER_NOT_NULL(parser_state); char next_char = parser_state->container_char_ptr[parser_state->index]; switch (next_char) { case '}': { PyErr_SetString(PyExc_TypeError, "End of dict reached instead " "of complete type"); return NULL; } case ')': { PyErr_SetString(PyExc_TypeError, "End of struct reached " "instead of complete type"); return NULL; } case '(': { // Struct == Tuple CALL_PYTHON_EXPECT_NONE(_parse_struct(complete_obj, parser_state)); break; } case '{': { // Dict PyErr_SetString(PyExc_TypeError, "D-Bus dict can't be outside of array"); return NULL; break; } case 'a': { // Array CALL_PYTHON_EXPECT_NONE(_parse_array(complete_obj, parser_state)); break; } case 'v': { // Variant == (signature, data)) CALL_PYTHON_EXPECT_NONE(_parse_variant(complete_obj, parser_state)); break; } default: { // Basic type CALL_PYTHON_EXPECT_NONE(_parse_basic(complete_obj, parser_state)); break; } } Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBusMessage_append_data(SdBusMessageObject* self, PyObject* const* args, Py_ssize_t nargs) { if (nargs < 2) { PyErr_SetString(PyExc_TypeError, "Minimum 2 args required"); return NULL; } SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); const char* signature_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); _Parse_state parser_state = { .message = self->message_ref, .container_char_ptr = signature_char_ptr, .index = 0, .max_index = strlen(signature_char_ptr), }; for (Py_ssize_t i = 1; i < nargs; ++i) { CALL_PYTHON_EXPECT_NONE(_parse_complete(args[i], &parser_state)); } #else static PyObject* SdBusMessage_append_data(SdBusMessageObject* self, PyObject* args) { Py_ssize_t num_args = PyTuple_Size(args); if (num_args < 2) { PyErr_SetString(PyExc_TypeError, "Minimum 2 args required"); return NULL; } PyObject* signature_str = PyTuple_GetItem(args, 0); PyObject* signature_bytes CLEANUP_PY_OBJECT = SD_BUS_PY_UNICODE_AS_BYTES(signature_str); const char* signature_char_ptr = SD_BUS_PY_BYTES_AS_CHAR_PTR(signature_bytes); _Parse_state parser_state = { .message = self->message_ref, .container_char_ptr = signature_char_ptr, .index = 0, .max_index = strlen(signature_char_ptr), }; for (Py_ssize_t i = 1; i < num_args; ++i) { CALL_PYTHON_EXPECT_NONE(_parse_complete(PyTuple_GetItem(args, i), &parser_state)); } #endif Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBusMessage_open_container(SdBusMessageObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); const char* container_type_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* container_contents_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); #else static PyObject* SdBusMessage_open_container(SdBusMessageObject* self, PyObject* args) { const char* container_type_char_ptr = NULL; const char* container_contents_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ss", &container_type_char_ptr, &container_contents_char_ptr, NULL)); #endif CALL_SD_BUS_AND_CHECK(sd_bus_message_open_container(self->message_ref, container_type_char_ptr[0], container_contents_char_ptr)); Py_RETURN_NONE; } static PyObject* SdBusMessage_close_container(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_message_close_container(self->message_ref)); Py_RETURN_NONE; } #ifndef Py_LIMITED_API static PyObject* SdBusMessage_enter_container(SdBusMessageObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); const char* container_type_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* container_contents_char_ptr = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); #else static PyObject* SdBusMessage_enter_container(SdBusMessageObject* self, PyObject* args) { const char* container_type_char_ptr = NULL; const char* container_contents_char_ptr = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ss", &container_type_char_ptr, &container_contents_char_ptr, NULL)); #endif CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(self->message_ref, container_type_char_ptr[0], container_contents_char_ptr)); Py_RETURN_NONE; } static PyObject* SdBusMessage_exit_container(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(self->message_ref)); Py_RETURN_NONE; } static SdBusMessageObject* SdBusMessage_create_reply(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { SdBusMessageObject* new_reply_message CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK(sd_bus_message_new_method_return(self->message_ref, &new_reply_message->message_ref)); Py_INCREF(new_reply_message); return new_reply_message; } static PyObject* SdBusMessage_send(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { CALL_SD_BUS_AND_CHECK(sd_bus_send(NULL, self->message_ref, NULL)); Py_RETURN_NONE; } static size_t _container_size(const char* container_sig) { size_t container_size = 0; size_t index = 0; while ((container_sig[index]) != '\0') { char current_char = container_sig[index]; index++; if (current_char == 'a') { index = _find_array_end(container_sig, index); index++; } if (current_char == '(') { index = _find_struct_end(container_sig, index); index++; } if (index == 0) { PyErr_SetString(PyExc_TypeError, "Failed to find container size"); return 0; } container_size++; } return container_size; } static PyObject* _iter_complete(_Parse_state* parser); static PyObject* _iter_basic(sd_bus_message* message, char basic_type) { switch (basic_type) { case 'b': { int new_int = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_int)); return PyBool_FromLong(new_int); break; } case 'y': { uint8_t new_char = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_char)); return PyLong_FromUnsignedLong((unsigned long)new_char); break; } case 'n':; int16_t new_short = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_short)); return PyLong_FromLong((long)new_short); break; case 'i': { int32_t new_long = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_long)); return PyLong_FromLong((long)new_long); break; } case 'x': { int64_t new_long_long = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_long_long)); return PyLong_FromLongLong((long long)new_long_long); break; } case 'q': { uint16_t new_u_short = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_u_short)); return PyLong_FromUnsignedLong((unsigned long)new_u_short); break; } case 'u': { uint32_t new_u_long = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_u_long)); return PyLong_FromUnsignedLong((unsigned long)new_u_long); break; } case 't': { uint64_t new_u_long_long = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_u_long_long)); return PyLong_FromUnsignedLongLong((unsigned long long)new_u_long_long); break; } case 'd': { double new_double = 0.0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_double)); return PyFloat_FromDouble(new_double); break; } case 'h': { int new_fd = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_fd)); return PyLong_FromLong((long)new_fd); break; } case 'g': case 'o': case 's': { const char* new_string = NULL; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_basic(message, basic_type, &new_string)); return PyUnicode_FromString(new_string); break; } default: { int code = (int)basic_type; PyObject* error_string CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_FromFormat("%c", code)); PyErr_Format(PyExc_TypeError, "Dbus type %R is unknown", error_string); return NULL; break; } } } static PyObject* _iter_bytes_array(_Parse_state* parser) { // Byte array const void* char_array = NULL; size_t array_size = 0; CALL_SD_BUS_AND_CHECK(sd_bus_message_read_array(parser->message, 'y', &char_array, &array_size)); return PyBytes_FromStringAndSize(char_array, (Py_ssize_t)array_size); } static PyObject* _iter_dict(_Parse_state* parser) { PyObject* new_dict CLEANUP_PY_OBJECT = PyDict_New(); char peek_type = '\0'; const char* container_type = NULL; while (CALL_SD_BUS_AND_CHECK(sd_bus_message_peek_type(parser->message, &peek_type, &container_type)) > 0) { if (peek_type != SD_BUS_TYPE_DICT_ENTRY) { PyErr_SetString(PyExc_TypeError, "Expected dict entry."); return NULL; } CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(parser->message, peek_type, container_type)); PyObject* key_object CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(_iter_basic(parser->message, container_type[0])); PyObject* value_object CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(_iter_complete(parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(parser->message)); if (PyDict_SetItem(new_dict, key_object, value_object) < 0) { return NULL; } } Py_INCREF(new_dict); return new_dict; } static PyObject* _iter_array(_Parse_state* parser) { PyObject* new_list CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyList_New(0)); char peek_type = '\0'; const char* container_type = NULL; while (CALL_SD_BUS_AND_CHECK(sd_bus_message_peek_type(parser->message, &peek_type, &container_type)) > 0) { PyObject* new_object CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(_iter_complete(parser)); if (PyList_Append(new_list, new_object) < 0) { return NULL; } } Py_INCREF(new_list); return new_list; } static PyObject* _iter_struct(_Parse_state* parser) { const char* container_sig = sd_bus_message_get_signature(parser->message, 0); if (container_sig == NULL) { PyErr_SetString(PyExc_TypeError, "Failed to get container signature"); return NULL; } size_t tuple_size = _container_size(container_sig); if (tuple_size == 0) { return NULL; } PyObject* new_tuple CLEANUP_PY_OBJECT = PyTuple_New((Py_ssize_t)tuple_size); for (size_t i = 0; i < tuple_size; ++i) { PyObject* new_complete = CALL_PYTHON_AND_CHECK(_iter_complete(parser)); SD_BUS_PY_TUPLE_SET_ITEM(new_tuple, i, new_complete); } Py_INCREF(new_tuple); return new_tuple; } static PyObject* _iter_variant(_Parse_state* parser) { const char* container_sig = sd_bus_message_get_signature(parser->message, 0); PyObject* value_object CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(_iter_complete(parser)); PyObject* variant_sig_str CLEANUP_PY_OBJECT = CALL_PYTHON_AND_CHECK(PyUnicode_FromString(container_sig)); return PyTuple_Pack(2, variant_sig_str, value_object); } static PyObject* _iter_complete(_Parse_state* parser) { const char* container_signature = NULL; char complete_type = '\0'; // TODO: can be optimized with custom parser instead of constantly // peeking CALL_SD_BUS_AND_CHECK(sd_bus_message_peek_type(parser->message, &complete_type, &container_signature)); switch (complete_type) { case 'a': { if (strcmp(container_signature, "y") == 0) { return _iter_bytes_array(parser); } if (container_signature[0] == '{') { CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(parser->message, complete_type, container_signature)); PyObject* new_dict = CALL_PYTHON_AND_CHECK(_iter_dict(parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(parser->message)); return new_dict; } CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(parser->message, complete_type, container_signature)); PyObject* new_array = CALL_PYTHON_AND_CHECK(_iter_array(parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(parser->message)); return new_array; break; } case 'v': { CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(parser->message, complete_type, container_signature)); PyObject* new_variant = CALL_PYTHON_AND_CHECK(_iter_variant(parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(parser->message)); return new_variant; break; } case 'r': { CALL_SD_BUS_AND_CHECK(sd_bus_message_enter_container(parser->message, complete_type, container_signature)); PyObject* new_tuple = CALL_PYTHON_AND_CHECK(_iter_struct(parser)); CALL_SD_BUS_AND_CHECK(sd_bus_message_exit_container(parser->message)); return new_tuple; break; } default: { return _iter_basic(parser->message, complete_type); break; } } } static PyObject* iter_tuple_or_single(_Parse_state* parser) { // Calculate the length of message data size_t container_size = _container_size(parser->container_char_ptr); if (container_size == 0) { return NULL; } if (container_size == 1) { return _iter_complete(parser); } else { return _iter_struct(parser); } } static PyObject* SdBusMessage_get_contents2(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { const char* message_signature = sd_bus_message_get_signature(self->message_ref, 0); if (message_signature == NULL) { PyErr_SetString(PyExc_TypeError, "Failed to get message signature."); return NULL; } if (message_signature[0] == '\0') { // Empty message Py_RETURN_NONE; } CALL_SD_BUS_AND_CHECK(sd_bus_message_rewind(self->message_ref, 0)); _Parse_state read_parser = { .message = self->message_ref, .container_char_ptr = message_signature, .index = 0, .max_index = strlen(message_signature), }; /* Parsing strategy Either return a single object (single string, single int, single array) or a tuple of single objects. This mirrors the python function returns. */ return iter_tuple_or_single(&read_parser); } static PyObject* SdBusMessage_parse_to_tuple(SdBusMessageObject* self, PyObject* Py_UNUSED(args)) { const char* message_signature = sd_bus_message_get_signature(self->message_ref, 0); if (message_signature == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get message signature."); return NULL; } if (message_signature[0] == '\0') { // Empty message. Return zero size tuple. return PyTuple_New(0); } CALL_SD_BUS_AND_CHECK(sd_bus_message_rewind(self->message_ref, 0)); _Parse_state read_parser = { .message = self->message_ref, .container_char_ptr = message_signature, .index = 0, .max_index = strlen(message_signature), }; return _iter_struct(&read_parser); } #ifndef Py_LIMITED_API static SdBusMessageObject* SdBusMessage_create_error_reply(SdBusMessageObject* self, PyObject* const* args, Py_ssize_t nargs) { SD_BUS_PY_CHECK_ARGS_NUMBER(2); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(0, PyUnicode_Check); SD_BUS_PY_CHECK_ARG_CHECK_FUNC(1, PyUnicode_Check); const char* name = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[0]); const char* error_message = SD_BUS_PY_UNICODE_AS_CHAR_PTR(args[1]); #else static SdBusMessageObject* SdBusMessage_create_error_reply(SdBusMessageObject* self, PyObject* args) { const char* name = NULL; const char* error_message = NULL; CALL_PYTHON_BOOL_CHECK(PyArg_ParseTuple(args, "ss", &name, &error_message, NULL)); #endif SdBusMessageObject* new_reply_message CLEANUP_SD_BUS_MESSAGE = (SdBusMessageObject*)CALL_PYTHON_AND_CHECK(SD_BUS_PY_CLASS_DUNDER_NEW(SdBusMessage_class)); CALL_SD_BUS_AND_CHECK(sd_bus_message_new_method_errorf(self->message_ref, &new_reply_message->message_ref, name, "%s", error_message)); Py_INCREF(new_reply_message); return new_reply_message; } static PyMethodDef SdBusMessage_methods[] = { {"append_data", (SD_BUS_PY_FUNC_TYPE)SdBusMessage_append_data, SD_BUS_PY_METH, PyDoc_STR("Append basic data based on signature.")}, {"open_container", (SD_BUS_PY_FUNC_TYPE)SdBusMessage_open_container, SD_BUS_PY_METH, PyDoc_STR("Open container for writing.")}, {"close_container", (PyCFunction)SdBusMessage_close_container, METH_NOARGS, PyDoc_STR("Close container.")}, {"enter_container", (SD_BUS_PY_FUNC_TYPE)SdBusMessage_enter_container, SD_BUS_PY_METH, PyDoc_STR("Enter container for reading.")}, {"exit_container", (PyCFunction)SdBusMessage_exit_container, METH_NOARGS, PyDoc_STR("Exit container.")}, {"dump", (PyCFunction)SdBusMessage_dump, METH_NOARGS, PyDoc_STR("Dump message to stdout.")}, {"seal", (PyCFunction)SdBusMessage_seal, METH_NOARGS, PyDoc_STR("Seal message contents.")}, {"get_contents", (PyCFunction)SdBusMessage_get_contents2, METH_NOARGS, PyDoc_STR("Iterate over message contents.")}, {"parse_to_tuple", (PyCFunction)SdBusMessage_parse_to_tuple, METH_NOARGS, PyDoc_STR("Parse message data to a tuple.")}, {"create_reply", (PyCFunction)SdBusMessage_create_reply, METH_NOARGS, PyDoc_STR("Create reply message.")}, {"create_error_reply", (SD_BUS_PY_FUNC_TYPE)SdBusMessage_create_error_reply, SD_BUS_PY_METH, PyDoc_STR("Create error reply with error name and error message.")}, {"send", (PyCFunction)SdBusMessage_send, METH_NOARGS, PyDoc_STR("Queue message to be sent.")}, {NULL, NULL, 0, NULL}, }; static PyObject* SdBusMessage_expect_reply_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { return PyBool_FromLong(CALL_SD_BUS_AND_CHECK(sd_bus_message_get_expect_reply(self->message_ref))); } static int SdBusMessage_expect_reply_setter(SdBusMessageObject* self, PyObject* new_value, void* Py_UNUSED(closure)) { if (NULL == new_value) { PyErr_SetString(PyExc_AttributeError, "Can't delete expect_reply"); return -1; } if (!PyBool_Check(new_value)) { PyErr_Format(PyExc_TypeError, "Expected bool, got %R", new_value); return -1; } CALL_SD_BUS_CHECK_RETURN_NEG1(sd_bus_message_set_expect_reply(self->message_ref, Py_True == new_value)); return 0; } static PyObject* SdBusMessage_destination_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { const char* destination_char_ptr = sd_bus_message_get_destination(self->message_ref); if (NULL != destination_char_ptr) { return PyUnicode_FromString(destination_char_ptr); } else { Py_RETURN_NONE; } } static PyObject* SdBusMessage_path_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { const char* path_char_ptr = sd_bus_message_get_path(self->message_ref); if (NULL != path_char_ptr) { return PyUnicode_FromString(path_char_ptr); } else { Py_RETURN_NONE; } } static PyObject* SdBusMessage_interface_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { const char* interface_char_ptr = sd_bus_message_get_interface(self->message_ref); if (NULL != interface_char_ptr) { return PyUnicode_FromString(interface_char_ptr); } else { Py_RETURN_NONE; } } static PyObject* SdBusMessage_member_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { const char* member_char_ptr = sd_bus_message_get_member(self->message_ref); if (NULL != member_char_ptr) { return PyUnicode_FromString(member_char_ptr); } else { Py_RETURN_NONE; } } static PyObject* SdBusMessage_sender_getter(SdBusMessageObject* self, void* Py_UNUSED(closure)) { const char* sender_char_ptr = sd_bus_message_get_sender(self->message_ref); if (NULL != sender_char_ptr) { return PyUnicode_FromString(sender_char_ptr); } else { Py_RETURN_NONE; } } static PyGetSetDef SdBusMessage_properies[] = { {"expect_reply", (getter)SdBusMessage_expect_reply_getter, (setter)SdBusMessage_expect_reply_setter, PyDoc_STR("Expect reply message?"), NULL}, {"destination", (getter)SdBusMessage_destination_getter, NULL, PyDoc_STR("Message destination service name."), NULL}, {"path", (getter)SdBusMessage_path_getter, NULL, PyDoc_STR("Message destination object path."), NULL}, {"interface", (getter)SdBusMessage_interface_getter, NULL, PyDoc_STR("Message destination interface name."), NULL}, {"member", (getter)SdBusMessage_member_getter, NULL, PyDoc_STR("Message destination member name."), NULL}, {"sender", (getter)SdBusMessage_sender_getter, NULL, PyDoc_STR("Message sender name."), NULL}, {0}, }; PyType_Spec SdBusMessageType = { .name = "sd_bus_internals.SdBusMessage", .basicsize = sizeof(SdBusMessageObject), .itemsize = 0, .flags = Py_TPFLAGS_DEFAULT, .slots = (PyType_Slot[]){ {Py_tp_new, PyType_GenericNew}, {Py_tp_dealloc, (destructor)SdBusMessage_dealloc}, {Py_tp_methods, SdBusMessage_methods}, {Py_tp_getset, SdBusMessage_properies}, {0, NULL}, }, }; python-sdbus-0.14.0/src/sdbus/unittest.py000066400000000000000000000163361477456016000204160ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import Event, TimeoutError, wait_for from contextlib import ExitStack, contextmanager from operator import setitem from os import environ, kill from pathlib import Path from signal import SIGTERM from subprocess import DEVNULL from subprocess import run as subprocess_run from tempfile import TemporaryDirectory from typing import TYPE_CHECKING from unittest import IsolatedAsyncioTestCase from weakref import ref as weak_ref from .dbus_proxy_async_signal import DbusLocalSignalAsync, DbusProxySignalAsync from .default_bus import _get_defaul_bus_tls, _set_default_bus_tls from .sd_bus_internals import SdBusMessage, sd_bus_open_user if TYPE_CHECKING: from collections.abc import Iterator from contextlib import AbstractAsyncContextManager from typing import Any, Optional, TypeVar, Union from .dbus_proxy_async_signal import ( DbusBoundSignalAsyncBase, DbusSignalAsync, ) from .sd_bus_internals import SdBus, SdBusSlot T = TypeVar('T') dbus_config = ''' session {pidfile_path} EXTERNAL unix:path={socket_path} ''' class DbusSignalRecorderBase: def __init__( self, timeout: Union[int, float], ): self._timeout = timeout self._captured_data: list[Any] = [] self._ready_event = Event() self._callback_method = self._callback async def start(self) -> None: raise NotImplementedError async def stop(self) -> None: raise NotImplementedError async def __aenter__(self) -> DbusSignalRecorderBase: raise NotImplementedError async def __aexit__( self, exc_type: Any, exc_value: Any, traceback: Any, ) -> None: if exc_type is not None: return try: await wait_for(self._ready_event.wait(), timeout=self._timeout) except TimeoutError: raise AssertionError("D-Bus signal not captured.") from None def _callback(self, data: Any) -> None: if isinstance(data, SdBusMessage): data = data.get_contents() self._captured_data.append(data) self._ready_event.set() @property def output(self) -> list[Any]: return self._captured_data.copy() class DbusSignalRecorderRemote(DbusSignalRecorderBase): def __init__( self, timeout: Union[int, float], bus: SdBus, remote_signal: DbusProxySignalAsync[Any], ): super().__init__(timeout) self._bus = bus self._match_slot: Optional[SdBusSlot] = None self._remote_signal = remote_signal async def __aenter__(self) -> DbusSignalRecorderBase: self._match_slot = await self._remote_signal._register_match_slot( self._bus, self._callback_method, ) return self async def __aexit__( self, exc_type: Any, exc_value: Any, traceback: Any, ) -> None: try: await super().__aexit__(exc_type, exc_value, traceback) finally: if self._match_slot is not None: self._match_slot.close() class DbusSignalRecorderLocal(DbusSignalRecorderBase): def __init__( self, timeout: Union[int, float], local_signal: DbusLocalSignalAsync[Any], ): super().__init__(timeout) self._local_signal_ref: weak_ref[DbusSignalAsync[Any]] = ( weak_ref(local_signal.dbus_signal) ) async def __aenter__(self) -> DbusSignalRecorderBase: local_signal = self._local_signal_ref() if local_signal is None: raise RuntimeError local_signal.local_callbacks.add(self._callback_method) return self @contextmanager def _isolated_dbus( dbus_executable_name: str = "dbus-daemon", ) -> Iterator[SdBus]: with ExitStack() as exit_stack: temp_dir_path = Path( exit_stack.enter_context( TemporaryDirectory(prefix="python-sdbus-") ) ) dbus_socket_path = temp_dir_path / "test_dbus.socket" pid_path = temp_dir_path / "dbus.pid" dbus_config_file = temp_dir_path / "dbus.config" dbus_config_file.write_text( dbus_config.format( socket_path=dbus_socket_path, pidfile_path=pid_path ) ) subprocess_run( args=( dbus_executable_name, '--config-file', dbus_config_file, '--fork', ), stdin=DEVNULL, check=True, ) # D-Bus daemon exits once it forks and is initialized. dbus_pid = int(pid_path.read_text()) exit_stack.callback(kill, dbus_pid, SIGTERM) old_session_bus_address = environ.get("DBUS_SESSION_BUS_ADDRESS") if old_session_bus_address is not None: exit_stack.callback( setitem, environ, "DBUS_SESSION_BUS_ADDRESS", old_session_bus_address, ) else: exit_stack.callback( environ.pop, "DBUS_SESSION_BUS_ADDRESS", ) environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path={dbus_socket_path}" old_bus = _get_defaul_bus_tls() bus = sd_bus_open_user() _set_default_bus_tls(bus) exit_stack.callback(_set_default_bus_tls, old_bus) yield bus class IsolatedDbusTestCase(IsolatedAsyncioTestCase): def setUp(self) -> None: # TODO: Use enterContext from Python 3.11 _isolated_dbus_cm = _isolated_dbus() self.bus = _isolated_dbus_cm.__enter__() self.addCleanup(_isolated_dbus_cm.__exit__, None, None, None) def assertDbusSignalEmits( self, signal: DbusBoundSignalAsyncBase[Any], timeout: Union[int, float] = 1, ) -> AbstractAsyncContextManager[DbusSignalRecorderBase]: if isinstance(signal, DbusLocalSignalAsync): return DbusSignalRecorderLocal(timeout, signal) elif isinstance(signal, DbusProxySignalAsync): return DbusSignalRecorderRemote(timeout, self.bus, signal) else: raise TypeError("Unknown or unsupported signal class.") python-sdbus-0.14.0/src/sdbus/utils/000077500000000000000000000000001477456016000173145ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus/utils/__init__.py000066400000000000000000000022071477456016000214260ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from .parse import ( parse_get_managed_objects, parse_interfaces_added, parse_interfaces_removed, parse_properties_changed, ) __all__ = ( "parse_get_managed_objects", "parse_interfaces_added", "parse_interfaces_removed", "parse_properties_changed", ) python-sdbus-0.14.0/src/sdbus/utils/inspect.py000066400000000000000000000071241477456016000213370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from ..dbus_common_elements import DbusLocalObjectMeta, DbusRemoteObjectMeta from ..dbus_proxy_async_interface_base import DbusInterfaceBaseAsync from ..dbus_proxy_sync_interface_base import DbusInterfaceBase from ..default_bus import get_default_bus if TYPE_CHECKING: from typing import Optional, Union from ..sd_bus_internals import SdBus def _inspect_dbus_path_proxy( obj: object, dbus_meta: DbusRemoteObjectMeta, bus: SdBus, ) -> str: if bus != dbus_meta.attached_bus: raise LookupError( f"D-Bus proxy {obj!r} at {dbus_meta.object_path!r} path " f"is not attached to bus {bus!r}" ) return dbus_meta.object_path def _inspect_dbus_path_local( obj: object, dbus_meta: DbusLocalObjectMeta, bus: SdBus, ) -> str: attached_bus = dbus_meta.attached_bus object_path = dbus_meta.serving_object_path if attached_bus is None or object_path is None: raise LookupError( f"Local D-Bus object {obj!r} is not exported to any D-Bus" ) if bus != attached_bus: raise LookupError( f"Local D-Bus object {obj!r} at {dbus_meta.serving_object_path!r} " f"path is not attached to bus {bus!r}" ) return object_path def inspect_dbus_path( obj: Union[DbusInterfaceBase, DbusInterfaceBaseAsync], bus: Optional[SdBus] = None, ) -> str: """Return the D-Bus path of an object. If called on a D-Bus proxy returns path of the proxied object. If called on a local D-Bus object returns the exported D-Bus path. If object is not exported raises ``LookupError``. If called on an object that is unrelated to D-Bus raises ``TypeError``. The object's path is inspected in the context of the given bus and if the object is attached to a different bus the ``LookupError`` will be raised. If the bus argument is not given or is ``None`` the default bus will be checked against. :param obj: Object to inspect. :param bus: Bus to inspect against. If not given or is ``None`` the default bus will be used. :returns: D-Bus path of the object. *New in version 0.13.0.* """ if bus is None: bus = get_default_bus() if isinstance(obj, DbusInterfaceBase): return _inspect_dbus_path_proxy(obj, obj._dbus, bus) elif isinstance(obj, DbusInterfaceBaseAsync): dbus_meta = obj._dbus if isinstance(dbus_meta, DbusRemoteObjectMeta): return _inspect_dbus_path_proxy(obj, dbus_meta, bus) else: return _inspect_dbus_path_local(obj, dbus_meta, bus) else: raise TypeError(f"Expected D-Bus object got {obj!r}") __all__ = ( "inspect_dbus_path", ) python-sdbus-0.14.0/src/sdbus/utils/parse.py000066400000000000000000000340121477456016000210000ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import TYPE_CHECKING from ..dbus_common_funcs import _parse_properties_vardict from ..dbus_proxy_async_interface_base import ( DBUS_CLASS_TO_META, DBUS_INTERFACE_NAME_TO_CLASS, DbusInterfaceBaseAsync, ) from ..dbus_proxy_sync_interface_base import DbusInterfaceBase if TYPE_CHECKING: from collections.abc import Iterable from typing import Any, Literal, Optional, Union from ..dbus_proxy_async_interfaces import DBUS_PROPERTIES_CHANGED_TYPING InterfacesBaseClasses = Union[DbusInterfaceBaseAsync, DbusInterfaceBase] InterfacesBaseTypes = type[InterfacesBaseClasses] InterfacesInputElements = Union[InterfacesBaseClasses, InterfacesBaseTypes] InterfacesInput = Union[ InterfacesInputElements, Iterable[InterfacesInputElements], ] InterfacesToClassMap = dict[ frozenset[str], InterfacesBaseTypes, ] OnUnknownMember = Literal['error', 'ignore', 'reuse'] OnUnknownInterface = Literal['error', 'none'] ParseGetManaged = dict[ str, tuple[ Optional[InterfacesBaseTypes], dict[str, Any], ], ] def _interfaces_input_to_types( interfaces: InterfacesInput, ) -> tuple[InterfacesBaseTypes, ...]: if isinstance( interfaces, (DbusInterfaceBaseAsync, DbusInterfaceBase, type) ): return ( interfaces if isinstance(interfaces, type) else type(interfaces), ) else: return tuple(i if isinstance(i, type) else type(i) for i in interfaces) def parse_properties_changed( interface: InterfacesInputElements, properties_changed_data: DBUS_PROPERTIES_CHANGED_TYPING, on_unknown_member: OnUnknownMember = 'error', ) -> dict[str, Any]: """Parse data from :py:meth:`properties_changed \ ` signal. Parses changed properties from a single D-Bus object. The object's interface class must be known in advance and passed as a first argument. Member names will be translated to python defined names. Invalidated properties will have a value of None. :param interface: Takes either D-Bus interface class or its object. :param properties_changed_data: Tuple caught from signal. :param on_unknown_member: If an unknown D-Bus property was encountered either raise an ``"error"`` (default), ``"ignore"`` the property or ``"reuse"`` the D-Bus name for the member. :returns: Dictionary of changed properties with keys translated to python names. Invalidated properties will have value of None. """ interface_name, changed_properties, invalidated_properties = ( properties_changed_data ) meta = DBUS_CLASS_TO_META[DBUS_INTERFACE_NAME_TO_CLASS[interface_name]] for invalidated_property in invalidated_properties: changed_properties[invalidated_property] = ('0', None) return _parse_properties_vardict( meta.dbus_member_to_python_attr, properties_changed_data[1], on_unknown_member, ) SKIP_INTERFACES = frozenset(( 'org.freedesktop.DBus.Properties', 'org.freedesktop.DBus.Introspectable', 'org.freedesktop.DBus.Peer', 'org.freedesktop.DBus.ObjectManager', )) def _create_interfaces_map( interfaces: tuple[InterfacesBaseTypes, ...], ) -> InterfacesToClassMap: interfaces_to_class_map: InterfacesToClassMap = {} for interface in interfaces: interface_names_set = frozenset( interface_name for interface_name, _ in interface._dbus_iter_interfaces_meta() if interface_name not in SKIP_INTERFACES ) interfaces_to_class_map[interface_names_set] = ( interface if isinstance(interface, type) else type(interface) ) return interfaces_to_class_map def _get_class_from_interfaces( interfaces_to_class_map: InterfacesToClassMap, interface_names_iter: Iterable[str], raise_key_error: bool, use_subset: bool, ) -> Optional[InterfacesBaseTypes]: class_set = frozenset(interface_names_iter) - SKIP_INTERFACES if use_subset: for interface_available in sorted( interfaces_to_class_map.keys(), key=len, reverse=True, ): if interface_available.issubset(class_set): class_set = interface_available break try: return interfaces_to_class_map[class_set] except KeyError: if raise_key_error: raise return None def _get_member_map_from_class( python_class: Optional[InterfacesBaseTypes], ) -> dict[str, dict[str, str]]: if python_class is None: return {} else: return { interface_name: meta.dbus_member_to_python_attr for interface_name, meta in python_class._dbus_iter_interfaces_meta() } def _translate_and_merge_members( properties_data: dict[str, dict[str, Any]], dbus_to_python_map: dict[str, dict[str, str]], on_unknown_member: OnUnknownMember, ) -> dict[str, Any]: python_properties: dict[str, Any] = {} for interface_name, properties in properties_data.items(): interface_member_map = dbus_to_python_map.get( interface_name, {}, ) python_properties.update( _parse_properties_vardict( interface_member_map, properties, on_unknown_member, ) ) return python_properties def parse_interfaces_added( interfaces: InterfacesInput, interfaces_added_data: tuple[str, dict[str, dict[str, Any]]], on_unknown_interface: OnUnknownInterface = 'error', on_unknown_member: OnUnknownMember = 'error', *, use_interface_subsets: bool = False, ) -> tuple[str, Optional[InterfacesBaseTypes], dict[str, Any]]: """Parse data from :py:meth:`interfaces_added \ ` signal. Takes the possible interface classes and the signal data. Returns the path of new object, the class of the added object (if it matched one of passed interface classes) and the dictionary of python named properties and their values. The passed interfaces can be async or blocking, the class or an instantiated object, a single item or an iterable of interfaces. :param interfaces: Possible interfaces that were added. :param interfaces_added_data: Tuple caught from signal. :param on_unknown_interface: If an unknown D-Bus interface was encountered either raise an ``"error"`` (default) or return ``"none"`` instead of interface class. :param on_unknown_member: If an unknown D-Bus property was encountered either raise an ``"error"`` (default), ``"ignore"`` the property or ``"reuse"`` the D-Bus name for the member. :param use_interface_subsets: Use the subset of interfaces as a valid match. For example, the class that implements ``org.example.foo`` would be matched with an data consising of both ``org.example.foo`` and ``org.example.bar``. The classes implementing more interfaces will have higher priority over the ones implementing fewer. :returns: Path of new added object, object's class (or ``None``) and dictionary of python translated members and their values. """ interfaces_types = _interfaces_input_to_types(interfaces) interfaces_to_class_map = _create_interfaces_map(interfaces_types) path, properties_data = interfaces_added_data python_class = ( _get_class_from_interfaces( interfaces_to_class_map, properties_data.keys(), on_unknown_interface == "error", use_interface_subsets, ) ) dbus_to_python_member_map = _get_member_map_from_class(python_class) python_properties: dict[str, Any] = {} for interface_name, properties in properties_data.items(): interface_member_map = dbus_to_python_member_map.get( interface_name, {}, ) python_properties.update( _parse_properties_vardict( interface_member_map, properties, on_unknown_member, ) ) return ( path, python_class, _translate_and_merge_members( properties_data, dbus_to_python_member_map, on_unknown_member, ), ) def parse_interfaces_removed( interfaces: InterfacesInput, interfaces_removed_data: tuple[str, list[str]], on_unknown_interface: OnUnknownInterface = 'error', *, use_interface_subsets: bool = False, ) -> tuple[str, Optional[InterfacesBaseTypes]]: """Parse data from :py:meth:`interfaces_added \ ` signal. Takes the possible interface classes and the signal data. Returns the path and the matched class of removed object. (if it matched one of passed interface classes) The passed interfaces can be async or blocking, the class or an instantiated object, a single item or an iterable of interfaces. :param interfaces: Possible interfaces that were removed. :param interfaces_added_data: Tuple caught from signal. :param on_unknown_member: If an unknown D-Bus interface was encountered either raise an ``"error"`` (default) or return ``"none"`` instead of interface class. :param use_interface_subsets: Use the subset of interfaces as a valid match. For example, the class that implements ``org.example.foo`` would be matched with an data consising of both ``org.example.foo`` and ``org.example.bar``. The classes implementing more interfaces will have higher priority over the ones implementing fewer. :returns: Path of removed object and object's class (or ``None``). """ interfaces_types = _interfaces_input_to_types(interfaces) interfaces_to_class_map = _create_interfaces_map(interfaces_types) path, interfaces_removed = interfaces_removed_data python_class = ( _get_class_from_interfaces( interfaces_to_class_map, interfaces_removed, on_unknown_interface == "error", use_interface_subsets, ) ) return path, python_class def parse_get_managed_objects( interfaces: InterfacesInput, managed_objects_data: dict[str, dict[str, dict[str, Any]]], on_unknown_interface: OnUnknownInterface = 'error', on_unknown_member: OnUnknownMember = 'error', *, use_interface_subsets: bool = False, ) -> ParseGetManaged: """Parse data from :py:meth:`get_managed_objects \ ` call. Takes the possible interface classes and the method's returned data. Returns a dictionary where keys a paths of the managed objects and value is a tuple of class of the object and dictionary of its python named properties and their values. The passed interfaces can be async or blocking, the class or an instantiated object, a single item or an iterable of interfaces. :param interfaces: Possible interfaces of the managed objects. :param managed_objects_data: Data returned by ``get_managed_objects`` call. :param on_unknown_interface: If an unknown D-Bus interface was encountered either raise an ``"error"`` (default) or return ``"none"`` instead of interface class. :param on_unknown_member: If an unknown D-Bus property was encountered either raise an ``"error"`` (default), ``"ignore"`` the property or ``"reuse"`` the D-Bus name for the member. :param use_interface_subsets: Use the subset of interfaces as a valid match. For example, the class that implements ``org.example.foo`` would be matched with an data consising of both ``org.example.foo`` and ``org.example.bar``. The classes implementing more interfaces will have higher priority over the ones implementing fewer. :returns: Dictionary where keys are paths and values are tuples of managed objects classes and their properties data. *New in version 0.12.0.* """ interfaces_types = _interfaces_input_to_types(interfaces) interfaces_to_class_map = _create_interfaces_map(interfaces_types) managed_objects_map: ParseGetManaged = {} for path, properties_data in managed_objects_data.items(): python_class = ( _get_class_from_interfaces( interfaces_to_class_map, properties_data.keys(), on_unknown_interface == "error", use_interface_subsets, ) ) dbus_to_python_member_map = _get_member_map_from_class(python_class) managed_objects_map[path] = ( python_class, _translate_and_merge_members( properties_data, dbus_to_python_member_map, on_unknown_member, ), ) return managed_objects_map __all__ = ( 'parse_properties_changed', 'parse_interfaces_added', 'parse_interfaces_removed', 'parse_get_managed_objects', ) python-sdbus-0.14.0/src/sdbus_async/000077500000000000000000000000001477456016000173515ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus_async/dbus_daemon/000077500000000000000000000000001477456016000216315ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus_async/dbus_daemon/__init__.py000066400000000000000000000133161477456016000237460ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import Optional from sdbus import ( DbusInterfaceCommonAsync, SdBus, dbus_method_async, dbus_property_async, dbus_signal_async, ) class FreedesktopDbus(DbusInterfaceCommonAsync, interface_name='org.freedesktop.DBus'): """D-Bus daemon.""" def __init__(self, bus: Optional[SdBus] = None): """This is the D-Bus daemon interface. Used for querying D-Bus state. D-Bus interface object path and service name is predetermined. (at ``'org.freedesktop.DBus'``, ``'/org/freedesktop/DBus'``) :param SdBus bus: Optional D-Bus connection. If not passed the default D-Bus will be used. """ super().__init__() self._proxify( 'org.freedesktop.DBus', '/org/freedesktop/DBus', bus, ) @dbus_method_async('s', method_name='GetConnectionUnixProcessID') async def get_connection_pid(self, service_name: str) -> int: """Get process ID that owns a specified name. :param str service_name: Service name to query. :return: PID of name owner :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method_async('s', method_name='GetConnectionUnixUser') async def get_connection_uid(self, service_name: str) -> int: """Get process user ID that owns a specified name. :param str service_name: Service name to query. :return: User ID of name owner :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method_async() async def get_id(self) -> str: """Returns machine id where bus is run. (stored in ``/etc/machine-id``) :return: Machine id """ raise NotImplementedError @dbus_method_async('s') async def get_name_owner(self, service_name: str) -> str: """Returns unique bus name (i.e. ``':1.94'``) for given service name. :param str service_name: Service name to query. :return: Unique bus name. :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method_async() async def list_activatable_names(self) -> list[str]: """Lists all activatable services names. :return: List of all names. """ raise NotImplementedError @dbus_method_async() async def list_names(self) -> list[str]: """List all services and connections currently of the bus. :return: List of all current names. """ raise NotImplementedError @dbus_method_async('s') async def name_has_owner(self, service_name: str) -> bool: """ Return True if someone already owns the name, False if nobody does. :param str service_name: Service name to query. :return: Is the name owned? """ raise NotImplementedError @dbus_method_async('su') async def start_service_by_name( self, service_name: str, flags: int = 0) -> int: """Starts a specified service. Flags parameter is not used currently and should be omitted or set to 0. :param str service_name: Service name to start. :param int flags: Not used. Omit or pass 0. :return: 1 on success, 2 if already started. """ raise NotImplementedError @dbus_property_async('as') def features(self) -> list[str]: """List of D-Bus daemon features. Features include: * 'AppArmor' - Messages filtered by AppArmor on this bus. * 'HeaderFiltering' - Messages are filtered if they have incorrect \ header fields. * 'SELinux' - Messages filtered by SELinux on this bus. * 'SystemdActivation' - services activated by systemd if their \ .service file specifies a D-Bus name. """ raise NotImplementedError @dbus_property_async('as') def interfaces(self) -> list[str]: """Extra D-Bus daemon interfaces""" raise NotImplementedError @dbus_signal_async('s') def name_acquired(self) -> str: """Signal when current process acquires a bus name.""" raise NotImplementedError @dbus_signal_async('s') def name_lost(self) -> str: """Signal when current process loses a bus name.""" raise NotImplementedError @dbus_signal_async('sss') def name_owner_changed(self) -> tuple[str, str, str]: """Signal when some name on a bus changes owner. Is a tuple of: * The name that acquired or lost * Old owner (by unique bus name) or empty string if no one owned it * New owner (by unique bus name) or empty string if no one owns it now """ raise NotImplementedError python-sdbus-0.14.0/src/sdbus_async/dbus_daemon/py.typed000066400000000000000000000000001477456016000233160ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus_block/000077500000000000000000000000001477456016000173265ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus_block/dbus_daemon/000077500000000000000000000000001477456016000216065ustar00rootroot00000000000000python-sdbus-0.14.0/src/sdbus_block/dbus_daemon/__init__.py000066400000000000000000000114351477456016000237230ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from typing import Optional from sdbus import DbusInterfaceCommon, SdBus, dbus_method, dbus_property class FreedesktopDbus(DbusInterfaceCommon, interface_name='org.freedesktop.DBus'): """D-Bus daemon.""" def __init__(self, bus: Optional[SdBus] = None): """This is the D-Bus daemon interface. Used for querying D-Bus state. D-Bus interface object path and service name is predetermined. (at ``'org.freedesktop.DBus'``, ``'/org/freedesktop/DBus'``) :param SdBus bus: Optional D-Bus connection. If not passed the default D-Bus will be used. """ super().__init__( 'org.freedesktop.DBus', '/org/freedesktop/DBus', bus, ) @dbus_method('s', method_name='GetConnectionUnixProcessID') def get_connection_pid(self, service_name: str) -> int: """Get process ID that owns a specified name. :param str service_name: Service name to query. :return: PID of name owner :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method('s', method_name='GetConnectionUnixUser') def get_connection_uid(self, service_name: str) -> int: """Get process user ID that owns a specified name. :param str service_name: Service name to query. :return: User ID of name owner :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method() def get_id(self) -> str: """Returns machine id where bus is run. (stored in ``/etc/machine-id``) :return: Machine id """ raise NotImplementedError @dbus_method('s') def get_name_owner(self, service_name: str) -> str: """Returns unique bus name (i.e. ``':1.94'``) for given service name. :param str service_name: Service name to query. :return: Unique bus name. :raises DbusNameHasNoOwnerError: Nobody owns that name """ raise NotImplementedError @dbus_method() def list_activatable_names(self) -> list[str]: """Lists all activatable services names. :return: List of all names. """ raise NotImplementedError @dbus_method() def list_names(self) -> list[str]: """List all services and connections currently of the bus. :return: List of all current names. """ raise NotImplementedError @dbus_method('s') def name_has_owner(self, service_name: str) -> bool: """Return True if someone already owns the name, False if nobody does. :param str service_name: Service name to query. :return: Is the name owned? """ raise NotImplementedError @dbus_method('su') def start_service_by_name( self, service_name: str, flags: int = 0) -> int: """Starts a specified service. Flags parameter is not used currently and should be omitted or set to 0. :param str service_name: Service name to start. :param int flags: Not used. Omit or pass 0. :return: 1 on success, 2 if already started. """ raise NotImplementedError @dbus_property('as') def features(self) -> list[str]: """List of D-Bus daemon features. Features include: * 'AppArmor' - Messages filtered by AppArmor on this bus. * 'HeaderFiltering' - Messages are filtered if they have incorrect \ header fields. * 'SELinux' - Messages filtered by SELinux on this bus. * 'SystemdActivation' - services activated by systemd if their \ .service file specifies a D-Bus name. """ raise NotImplementedError @dbus_property('as') def interfaces(self) -> list[str]: """Extra D-Bus daemon interfaces""" raise NotImplementedError python-sdbus-0.14.0/src/sdbus_block/dbus_daemon/py.typed000066400000000000000000000000001477456016000232730ustar00rootroot00000000000000python-sdbus-0.14.0/test/000077500000000000000000000000001477456016000152245ustar00rootroot00000000000000python-sdbus-0.14.0/test/__init__.py000066400000000000000000000015151477456016000173370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA python-sdbus-0.14.0/test/benchmarks/000077500000000000000000000000001477456016000173415ustar00rootroot00000000000000python-sdbus-0.14.0/test/benchmarks/__init__.py000066400000000000000000000015521477456016000214550ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations python-sdbus-0.14.0/test/benchmarks/bench_async_ping.py000066400000000000000000000045051477456016000232100ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import gather from asyncio import run as asyncio_run from time import perf_counter import pyperf # type: ignore from sdbus.unittest import _isolated_dbus from sdbus import DbusInterfaceCommonAsync def bench_async_ping_gather(loops: int) -> float: with _isolated_dbus() as bus: dbus_interface = DbusInterfaceCommonAsync.new_proxy( "org.freedesktop.DBus", "/org/freedesktop/DBus", bus, ) async def run_ping_gather() -> float: gather_ping = gather( *(dbus_interface.dbus_ping() for _ in range(loops)) ) start = perf_counter() await gather_ping return perf_counter() - start return asyncio_run(run_ping_gather()) def bench_async_ping(loops: int) -> float: with _isolated_dbus() as bus: dbus_interface = DbusInterfaceCommonAsync.new_proxy( "org.freedesktop.DBus", "/org/freedesktop/DBus", bus, ) async def run_ping() -> float: start = perf_counter() for _ in range(loops): await dbus_interface.dbus_ping() return perf_counter() - start return asyncio_run(run_ping()) def main() -> None: runner = pyperf.Runner() runner.bench_time_func('sdbus_async_ping', bench_async_ping) runner.bench_time_func('sdbus_async_ping_gather', bench_async_ping_gather) if __name__ == "__main__": main() python-sdbus-0.14.0/test/common_test_util.py000066400000000000000000000031351477456016000211640ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import SkipTest, main def mem_test() -> None: while True: try: main() except SystemExit: ... def mem_test_single(test_class: type, test_name: str) -> None: while True: t = test_class() t.setUp() getattr(t, test_name)() def skip_if_no_asserts() -> None: try: assert False except AssertionError: return raise SkipTest("Assertions are not enabled") def skip_if_no_name_validations() -> None: skip_if_no_asserts() from sdbus.sd_bus_internals import is_interface_name_valid try: is_interface_name_valid("org.test") except NotImplementedError: raise SkipTest("Validation functions not available") python-sdbus-0.14.0/test/containers/000077500000000000000000000000001477456016000173715ustar00rootroot00000000000000python-sdbus-0.14.0/test/containers/Containerfile-alpine000066400000000000000000000010061477456016000233410ustar00rootroot00000000000000FROM docker.io/alpine COPY ../../src /root/python-sdbus/src COPY ../../test /root/python-sdbus/test COPY ../../setup.py ../../README.md /root/python-sdbus/ RUN apk update && \ apk upgrade && \ apk add python3 \ py3-setuptools \ python3-dev \ elogind-dev \ musl-dev \ gcc \ pkgconfig \ dbus \ py3-jinja2 WORKDIR /root/python-sdbus/ CMD python3 setup.py build --build-lib build-lib && \ PYTHONPATH=./build-lib python3 -m unittest --verbose python-sdbus-0.14.0/test/leak_tests.py000066400000000000000000000153041477456016000177370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import ( FIRST_EXCEPTION, Task, get_running_loop, sleep, wait, wait_for, ) from os import environ from resource import RUSAGE_SELF, getrusage from typing import Any, cast from unittest import SkipTest from sdbus.exceptions import DbusFailedError from sdbus.unittest import IsolatedDbusTestCase from sdbus import request_default_bus_name_async from .test_low_level_errors import ( DbusDerivePropertydError, InterfaceWithErrors, ) from .test_read_write_dbus_types import TestDbusTypes from .test_sdbus_async import TestPing, TestProxy, initialize_object ENABLE_LEAK_TEST_VAR = 'PYTHON_SDBUS_TEST_LEAKS' def leak_test_enabled() -> None: if not environ.get(ENABLE_LEAK_TEST_VAR): raise SkipTest( 'Leak tests not enabled, set ' f"{ENABLE_LEAK_TEST_VAR} env variable" 'to 1 to enable.' ) class LeakTests(IsolatedDbusTestCase): def setUp(self) -> None: super().setUp() self.start_mem = getrusage(RUSAGE_SELF).ru_maxrss def check_memory(self) -> None: current_usage = getrusage(RUSAGE_SELF).ru_maxrss if current_usage > self.start_mem * 2: raise RuntimeError('Leaking memory') def test_read_write_dbus_types(self) -> None: leak_test_enabled() pseudo_test = cast(TestDbusTypes, self) for _ in range(1_000_000): TestDbusTypes.test_unsigned(pseudo_test) TestDbusTypes.test_signed(pseudo_test) TestDbusTypes.test_strings(pseudo_test) TestDbusTypes.test_double(pseudo_test) TestDbusTypes.test_bool(pseudo_test) TestDbusTypes.test_array(pseudo_test) TestDbusTypes.test_empty_array(pseudo_test) TestDbusTypes.test_array_compound(pseudo_test) TestDbusTypes.test_nested_array(pseudo_test) TestDbusTypes.test_struct(pseudo_test) TestDbusTypes.test_dict(pseudo_test) TestDbusTypes.test_empty_dict(pseudo_test) TestDbusTypes.test_dict_nested_array(pseudo_test) TestDbusTypes.test_variant(pseudo_test) TestDbusTypes.test_array_of_variant(pseudo_test) TestDbusTypes.test_array_of_dict(pseudo_test) TestDbusTypes.test_array_of_struct(pseudo_test) TestDbusTypes.test_dict_of_struct(pseudo_test) TestDbusTypes.test_struct_with_dict(pseudo_test) TestDbusTypes.test_dict_of_array(pseudo_test) TestDbusTypes.test_array_of_array(pseudo_test) TestDbusTypes.test_sealed_message_append(pseudo_test) self.check_memory() async def test_ping(self) -> None: leak_test_enabled() pseudo_test = cast(TestPing, self) for _ in range(1_000_000): await TestPing.test_ping(pseudo_test) self.check_memory() async def test_objects(self) -> None: leak_test_enabled() await self.bus.request_name_async("org.example.test", 0) pseudo_test = cast(TestProxy, self) for _ in range(20_000): await TestProxy.test_method_kwargs(pseudo_test) await TestProxy.test_method(pseudo_test) await TestProxy.test_subclass(pseudo_test) await TestProxy.test_properties(pseudo_test) await TestProxy.test_signal(pseudo_test) await TestProxy.test_exceptions(pseudo_test) await TestProxy.test_no_reply_method(pseudo_test) await TestProxy.test_interface_remove(pseudo_test) TestProxy.test_docstring(pseudo_test) self.check_memory() async def test_low_level_errors(self) -> None: leak_test_enabled() await request_default_bus_name_async('org.test') self.test_object = InterfaceWithErrors() self.test_object.export_to_dbus('/') self.test_object_connection = InterfaceWithErrors.new_proxy( 'org.test', '/') loop = get_running_loop() def silence_exceptions(*args: Any, **kwrags: Any) -> None: ... loop.set_exception_handler(silence_exceptions) for _ in range(150_000): with self.assertRaises(DbusFailedError): await wait_for( self.test_object_connection.indep_err_getter.get_async(), timeout=1, ) with self.assertRaises(DbusDerivePropertydError): await wait_for( self.test_object_connection.derrive_err_getter.get_async(), timeout=1, ) self.check_memory() async def test_single_object(self) -> None: leak_test_enabled() await self.bus.request_name_async("org.example.test", 0) test_object, test_object_connection = initialize_object() i = 0 num_of_iterations = 10_000 num_of_tasks = 5 async def the_test() -> None: for _ in range(num_of_iterations): self.assertEqual( 'ASD', await wait_for(test_object_connection.upper('asd'), 0.5)) await sleep(0) self.assertEqual( 'test_property', await wait_for(test_object_connection.test_property, 0.5)) await sleep(0) await wait_for( test_object_connection.test_property.set_async( 'test_property'), 0.5) await sleep(0) self.check_memory() nonlocal i i += 1 tasks: list[Task[None]] = [] loop = get_running_loop() for _ in range(num_of_tasks): tasks.append(loop.create_task(the_test())) done, pending = await wait(tasks, return_when=FIRST_EXCEPTION) self.check_memory() self.assertEqual(i, num_of_iterations * num_of_tasks) python-sdbus-0.14.0/test/test_default_bus.py000066400000000000000000000033031477456016000211310ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2025 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from contextvars import copy_context from unittest import main from sdbus.unittest import IsolatedDbusTestCase from sdbus import get_default_bus, sd_bus_open_user, set_context_default_bus def return_bus_id() -> int: return id(get_default_bus()) def set_context_and_return_id() -> int: set_context_default_bus(sd_bus_open_user()) return id(get_default_bus()) class TestDefaultBus(IsolatedDbusTestCase): def test_context_bus(self) -> None: bus_id = id(get_default_bus()) self.assertEqual( bus_id, copy_context().run(return_bus_id), ) self.assertNotEqual( bus_id, copy_context().run(set_context_and_return_id), ) self.assertEqual( bus_id, id(get_default_bus()), ) if __name__ == "__main__": main() python-sdbus-0.14.0/test/test_deprecations.py000066400000000000000000000016541477456016000213230ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import main if __name__ == '__main__': main() python-sdbus-0.14.0/test/test_high_level_errors.py000066400000000000000000000054531477456016000223460ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import get_running_loop, wait_for from typing import Any from sdbus.exceptions import DbusFailedError from sdbus.unittest import IsolatedDbusTestCase from sdbus import ( DbusInterfaceCommonAsync, dbus_method_async, request_default_bus_name_async, ) HELLO_WORLD = 'Hello, world!' class DbusDeriveMethodError(DbusFailedError): dbus_error_name = 'org.example.Method.Error' class IndependentError(Exception): ... GOOD_STR = 'Good' class InterfaceWithErrors( DbusInterfaceCommonAsync, interface_name='org.example.test', ): @dbus_method_async(result_signature='s') async def hello_independent_error(self) -> str: raise IndependentError @dbus_method_async(result_signature='s') async def hello_derrived_error(self) -> str: raise DbusDeriveMethodError class TestHighLevelErrors(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() await request_default_bus_name_async('org.test') self.test_object = InterfaceWithErrors() self.test_object.export_to_dbus('/') self.test_object_connection = InterfaceWithErrors.new_proxy( 'org.test', '/') loop = get_running_loop() def silence_exceptions(*args: Any, **kwrags: Any) -> None: ... loop.set_exception_handler(silence_exceptions) async def test_method_indenendent_error(self) -> None: with self.assertRaises(DbusFailedError) as cm: await wait_for( self.test_object_connection.hello_independent_error(), timeout=1, ) exception = cm.exception self.assertIs(exception.__class__, DbusFailedError) async def test_method_derived_error(self) -> None: with self.assertRaises(DbusDeriveMethodError): await wait_for( self.test_object_connection.hello_derrived_error(), timeout=1, ) python-sdbus-0.14.0/test/test_interface_generator.py000066400000000000000000000257211477456016000226520ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from importlib.util import find_spec from unittest import SkipTest, TestCase, main from unittest.mock import MagicMock, patch from sdbus.__main__ import generator_main from sdbus.interface_generator import ( DbusSigToTyping, camel_case_to_snake_case, generate_py_file, interface_name_to_class, interfaces_from_str, ) from sdbus.unittest import IsolatedDbusTestCase test_xml = """ """ class TestConverter(TestCase): def test_camel_to_snake(self) -> None: with self.subTest("CamelCase"): self.assertEqual( 'activate_connection', camel_case_to_snake_case('ActivateConnection'), ) with self.subTest("Already snake case"): self.assertEqual( 'activate_connection', camel_case_to_snake_case('activate_connection'), ) with self.subTest("Upper snake case"): self.assertEqual( 'activate_connection', camel_case_to_snake_case('ACTIVATE_CONNECTION'), ) def test_interface_name_to_class(self) -> None: self.assertEqual( 'ComExampleSampleInterface0', interface_name_to_class('com.example.SampleInterface0'), ) def test_signature_to_typing(self) -> None: with self.subTest('Parse basic'): self.assertEqual( 'str', DbusSigToTyping.typing_basic('s') ) self.assertRaises( KeyError, DbusSigToTyping.typing_basic, 'v') with self.subTest('Parse variant'): self.assertEqual( 'tuple[str, Any]', DbusSigToTyping.typing_complete('v') ) with self.subTest('Splitter test'): self.assertEqual( ['v', 'as', '(uisa{sx})', 'h', 'a(ss)', 'a{ss}', 'ay'], DbusSigToTyping.split_sig('vas(uisa{sx})ha(ss)a{ss}ay') ) with self.subTest('Parse struct'): self.assertEqual( DbusSigToTyping.typing_complete('(sx)'), 'tuple[str, int]', ) with self.subTest('Parse list'): self.assertEqual( DbusSigToTyping.typing_complete('a(sx)'), 'list[tuple[str, int]]', ) with self.subTest('Parse dict'): self.assertEqual( DbusSigToTyping.typing_complete('a{s(xh)}'), 'dict[str, tuple[int, int]]', ) with self.subTest('Parse signature'): self.assertEqual( DbusSigToTyping.sig_to_typing('a{s(xh)}'), 'dict[str, tuple[int, int]]', ) self.assertEqual( DbusSigToTyping.sig_to_typing('a{s(xh)}xs'), 'tuple[dict[str, tuple[int, int]], int, str]', ) self.assertEqual( DbusSigToTyping.sig_to_typing('a{s(xh)}xs'), 'tuple[dict[str, tuple[int, int]], int, str]', ) self.assertEqual( DbusSigToTyping.sig_to_typing('as'), 'list[str]', ) self.assertEqual( DbusSigToTyping.sig_to_typing(''), 'None', ) def test_parsing(self) -> None: if find_spec('jinja2') is None: raise SkipTest('Jinja2 not installed') interfaces_intro = interfaces_from_str(test_xml) with self.subTest('Test introspection details'): test_interface = interfaces_intro[0] for test_property in test_interface.properties: if test_property.method_name == 'BoundBy': self.assertEqual( test_property.emits_changed, 'const', ) elif test_property.method_name == 'Bar': self.assertEqual( test_property.emits_changed, True, ) elif test_property.method_name == 'FooInvalidates': self.assertEqual( test_property.emits_changed, 'invalidates', ) elif test_property.method_name == 'FooFoo': self.assertEqual( test_property.emits_changed, False, ) generated = generate_py_file(interfaces_intro) self.assertIn('flags=DbusPropertyEmitsInvalidationFlag', generated) self.assertIn('flags=DbusPropertyConstFlag', generated) class TestGeneratorAgainstDbus(IsolatedDbusTestCase): def setUp(self) -> None: if find_spec('jinja2') is None: raise SkipTest('Jinja2 not installed') super().setUp() def test_generate_from_connection(self) -> None: with patch("sdbus.__main__.stdout") as stdout_mock: generator_main( [ "gen-from-connection", "org.freedesktop.DBus", "/org/freedesktop/DBus", ] ) write_mock: MagicMock = stdout_mock.write write_mock.assert_called_once() generated_interface = write_mock.call_args.args[0] self.assertIn( "OrgFreedesktopDBusDebugStatsInterface", generated_interface, ) self.assertIn( "get_connection_unix_process_id", generated_interface, ) self.assertIn( "async", generated_interface, ) def test_generate_from_connection_blocking(self) -> None: with patch("sdbus.__main__.stdout") as stdout_mock: generator_main( [ "gen-from-connection", "--block", "org.freedesktop.DBus", "/org/freedesktop/DBus", ] ) write_mock: MagicMock = stdout_mock.write write_mock.assert_called_once() generated_interface = write_mock.call_args.args[0] self.assertNotIn( "async", generated_interface, ) self.assertIn( "dbus_property", generated_interface, ) INTERFACE_NO_MEMBERS_XML = """ """ class TestGeneratorSyntaxCompile(TestCase): def setUp(self) -> None: if find_spec('jinja2') is None: raise SkipTest('Jinja2 not installed') super().setUp() def test_syntax_compile_async(self) -> None: source_code = generate_py_file( interfaces_from_str(test_xml), do_async=True, ) compile(source_code, filename="", mode="exec") def test_syntax_compile_block(self) -> None: source_code = generate_py_file( interfaces_from_str(test_xml), do_async=False, ) compile(source_code, filename="", mode="exec") def test_syntax_no_members_interface(self) -> None: regular_interface = interfaces_from_str(test_xml) no_members_interface = interfaces_from_str(INTERFACE_NO_MEMBERS_XML) self.assertFalse(no_members_interface[0].methods) self.assertFalse(no_members_interface[0].properties) self.assertFalse(no_members_interface[0].signals) compile( generate_py_file( regular_interface + no_members_interface, do_async=True, ), filename="", mode="exec", ) compile( generate_py_file( no_members_interface + regular_interface, do_async=True, ), filename="", mode="exec", ) compile( generate_py_file( regular_interface + no_members_interface, do_async=False, ), filename="", mode="exec", ) compile( generate_py_file( no_members_interface + regular_interface, do_async=False, ), filename="", mode="exec", ) if __name__ == "__main__": main() python-sdbus-0.14.0/test/test_low_level_api.py000066400000000000000000000064531477456016000214660ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import get_running_loop from unittest import SkipTest, TestCase, main from sdbus.sd_bus_internals import ( SdBus, is_interface_name_valid, is_member_name_valid, is_object_path_valid, is_service_name_valid, ) from sdbus.unittest import IsolatedDbusTestCase class TestAsyncLowLevel(IsolatedDbusTestCase): def test_init_bus(self) -> None: not_connected_bus = SdBus() self.assertIsNone(not_connected_bus.address) self.assertIsNotNone(self.bus.address) async def test_bus_fd_unregister_close(self) -> None: await self.bus.request_name_async("org.example", 0) bus_fd = self.bus.get_fd() self.bus.close() loop = get_running_loop() self.assertFalse(loop.remove_reader(bus_fd)) self.assertFalse(loop.remove_writer(bus_fd)) class TestLowLeveApi(TestCase): def test_validation_funcs(self) -> None: try: self.assertTrue( is_interface_name_valid('org.example.test') ) self.assertFalse( is_interface_name_valid('Not very valid 😀') ) self.assertTrue( is_service_name_valid('org.example.test') ) self.assertFalse( is_service_name_valid('Not very valid 😀') ) self.assertTrue( is_member_name_valid('GetSomething') ) self.assertFalse( is_member_name_valid('no.dots.in.member.names') ) self.assertTrue( is_object_path_valid('/test') ) self.assertFalse( is_object_path_valid('no.dots.in.object.paths') ) except NotImplementedError: raise SkipTest( "Validation funcs not implemented. " "Probably too old libsystemd. (< 246)" ) def test_bus_method_call_timeout(self) -> None: bus = SdBus() self.assertIsNotNone(bus.method_call_timeout_usec) test_timeout_usec = 10 * 10**6 # 10 seconds bus.method_call_timeout_usec = test_timeout_usec self.assertEqual(test_timeout_usec, bus.method_call_timeout_usec) with self.assertRaises(TypeError): bus.method_call_timeout_usec = "test" # type: ignore with self.assertRaises(ValueError): del bus.method_call_timeout_usec if __name__ == "__main__": main() python-sdbus-0.14.0/test/test_low_level_errors.py000066400000000000000000000146321477456016000222270ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import get_running_loop, wait_for from typing import Any from sdbus.dbus_common_elements import DbusLocalObjectMeta from sdbus.exceptions import DbusFailedError from sdbus.unittest import IsolatedDbusTestCase from sdbus import ( DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, request_default_bus_name_async, ) HELLO_WORLD = 'Hello, world!' class DbusDerivePropertydError(DbusFailedError): dbus_error_name = 'org.example.PropertyError' class IndependentError(Exception): ... GOOD_STR = 'Good' class InterfaceWithErrors( DbusInterfaceCommonAsync, interface_name='org.example.errors', ): @dbus_property_async('s') def indep_err_getter(self) -> str: raise IndependentError @dbus_property_async('s') def derrive_err_getter(self) -> str: raise DbusDerivePropertydError @dbus_method_async(result_signature='s') async def hello_error(self) -> str: raise AttributeError @dbus_method_async(result_signature='s') async def hello_world(self) -> str: return HELLO_WORLD @dbus_property_async('s') def indep_err_setable(self) -> str: return GOOD_STR @indep_err_setable.setter def indep_err_setter(self, new_value: str) -> None: raise IndependentError @dbus_property_async('s') def derrive_err_settable(self) -> str: return GOOD_STR @derrive_err_settable.setter def derrive_err_setter(self, new_value: str) -> None: raise DbusDerivePropertydError class TestLowLevelErrors(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() await request_default_bus_name_async('org.test') self.test_object = InterfaceWithErrors() self.test_object.export_to_dbus('/') self.test_object_connection = InterfaceWithErrors.new_proxy( 'org.test', '/') loop = get_running_loop() def silence_exceptions(*args: Any, **kwrags: Any) -> None: ... loop.set_exception_handler(silence_exceptions) async def test_property_getter_independent_error(self) -> None: with self.assertRaises(DbusFailedError) as cm: await wait_for( self.test_object_connection.indep_err_getter.get_async(), timeout=1, ) should_be_dbus_failed = cm.exception self.assertIs(should_be_dbus_failed.__class__, DbusFailedError) await self.test_object_connection.hello_world() async def test_property_getter_derived_error(self) -> None: with self.assertRaises(DbusDerivePropertydError): await wait_for( self.test_object_connection.derrive_err_getter.get_async(), timeout=1, ) await self.test_object_connection.hello_world() async def test_property_setter_independent_error(self) -> None: self.assertEqual( await wait_for( self.test_object_connection.indep_err_setable.get_async(), timeout=1, ), GOOD_STR, ) with self.assertRaises(DbusFailedError) as cm: await wait_for( self.test_object_connection. indep_err_setable.set_async('Test'), timeout=1, ) should_be_dbus_failed = cm.exception self.assertIs(should_be_dbus_failed.__class__, DbusFailedError) await self.test_object_connection.hello_world() async def test_property_setter_derived_error(self) -> None: self.assertEqual( await wait_for( self.test_object_connection.derrive_err_settable.get_async(), timeout=1, ), GOOD_STR, ) with self.assertRaises(DbusDerivePropertydError): await wait_for( self.test_object_connection. derrive_err_settable.set_async('Test'), timeout=1, ) await self.test_object_connection.hello_world() async def test_property_callback_error(self) -> None: dbus_local_meta = self.test_object._dbus if not isinstance(dbus_local_meta, DbusLocalObjectMeta): raise TypeError interface = dbus_local_meta.activated_interfaces[0] interface.property_get_dict.pop(b'DerriveErrSettable') with self.assertRaises(DbusFailedError): await wait_for( self.test_object_connection.derrive_err_settable, timeout=1, ) async def test_method_callback_error(self) -> None: TEST_KEY = b'HelloWorld' dbus_local_meta = self.test_object._dbus if not isinstance(dbus_local_meta, DbusLocalObjectMeta): raise TypeError interface = dbus_local_meta.activated_interfaces[0] interface.method_dict.pop(TEST_KEY) with self.assertRaises(DbusFailedError): await wait_for( self.test_object_connection.hello_world(), timeout=1, ) interface.method_dict[TEST_KEY] = None with self.assertRaises(TypeError): await wait_for( self.test_object_connection.hello_world(), timeout=1, ) def test_raise(*args: Any, **kwargs: Any) -> None: raise DbusDerivePropertydError interface.method_dict[TEST_KEY] = test_raise with self.assertRaises(DbusDerivePropertydError): await wait_for( self.test_object_connection.hello_world(), timeout=1, ) python-sdbus-0.14.0/test/test_object_manager.py000066400000000000000000000330401477456016000215750ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2022 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from sdbus.exceptions import DbusUnknownObjectError from sdbus.unittest import IsolatedDbusTestCase from sdbus.utils import ( parse_get_managed_objects, parse_interfaces_added, parse_interfaces_removed, ) from sdbus import ( DbusInterfaceCommonAsync, DbusObjectManagerInterfaceAsync, dbus_method_async, dbus_property_async, ) HELLO_WORLD = 'Hello World!' TEST_NUMBER = 1000 class ObjectManagerTestInterface( DbusObjectManagerInterfaceAsync, interface_name='org.test.objectmanager', ): @dbus_method_async( result_signature='s', ) async def get_hello_world(self) -> str: return HELLO_WORLD OBJECT_MANAGER_PATH = '/object_manager' CONNECTION_NAME = 'org.example.test' MANAGED_INTERFACE_NAME = 'org.test.testing' class ManagedInterface( DbusInterfaceCommonAsync, interface_name=MANAGED_INTERFACE_NAME, ): @dbus_property_async('x') def test_int(self) -> int: return TEST_NUMBER MANAGED_PATH = '/object_manager/test' class TestObjectManager(IsolatedDbusTestCase): async def test_object_manager(self) -> None: await self.bus.request_name_async(CONNECTION_NAME, 0) object_manager = ObjectManagerTestInterface() object_manager.export_to_dbus(OBJECT_MANAGER_PATH) object_manager_connection = ObjectManagerTestInterface.new_proxy( CONNECTION_NAME, OBJECT_MANAGER_PATH) self.assertEqual( await object_manager_connection.get_hello_world(), HELLO_WORLD) managed_object = ManagedInterface() async with self.assertDbusSignalEmits( object_manager_connection.interfaces_added ) as added_interfaces_catch: object_manager.export_with_manager(MANAGED_PATH, managed_object) caught_added = added_interfaces_catch.output[0] added_path, added_attributes = caught_added self.assertEqual(added_path, MANAGED_PATH) self.assertEqual( added_attributes[ MANAGED_INTERFACE_NAME][ 'TestInt'][1], TEST_NUMBER, ) with self.subTest("Test interfaces added parser"): parse_interfaces_added(ManagedInterface, caught_added) async with self.assertDbusSignalEmits( object_manager_connection.interfaces_removed ) as removed_interfaces_catch: object_manager.remove_managed_object(managed_object) path_removed, interfaces_removed = removed_interfaces_catch.output[0] self.assertEqual(path_removed, MANAGED_PATH) self.assertIn(MANAGED_INTERFACE_NAME, interfaces_removed) def test_expot_with_no_manager(self) -> None: object_manager = ObjectManagerTestInterface() managed_object = ManagedInterface() self.assertRaises( RuntimeError, object_manager.export_with_manager, MANAGED_PATH, managed_object, ) async def test_parse_interfaces_added_removed(self) -> None: MANAGED_TWO_INTERFACE_NAME = MANAGED_INTERFACE_NAME + 'Two' class ManagedTwoInterface( ManagedInterface, interface_name=MANAGED_TWO_INTERFACE_NAME, ): @dbus_property_async('s') def test_str(self) -> str: return 'test' await self.bus.request_name_async(CONNECTION_NAME, 0) object_manager = DbusObjectManagerInterfaceAsync() object_manager.export_to_dbus(OBJECT_MANAGER_PATH) object_manager_connection = DbusObjectManagerInterfaceAsync.new_proxy( CONNECTION_NAME, OBJECT_MANAGER_PATH) managed_object = ManagedTwoInterface() async with self.assertDbusSignalEmits( object_manager_connection.interfaces_added ) as added_interfaces_catch: object_manager.export_with_manager(MANAGED_PATH, managed_object) caught_added = added_interfaces_catch.output[0] with self.subTest('Parse added class'): path, python_class, python_properties = ( parse_interfaces_added(ManagedTwoInterface, caught_added) ) self.assertEqual(path, MANAGED_PATH) self.assertEqual(python_class, ManagedTwoInterface) self.assertIn('test_str', python_properties) self.assertIn('test_int', python_properties) with self.subTest('Parse added object'): path, python_class, python_properties = ( parse_interfaces_added(managed_object, caught_added) ) self.assertEqual(path, MANAGED_PATH) self.assertEqual(python_class, ManagedTwoInterface) self.assertIn('test_str', python_properties) self.assertIn('test_int', python_properties) with self.subTest('Parse added iterable'): path, python_class, python_properties = ( parse_interfaces_added( (ManagedInterface, ManagedTwoInterface), caught_added) ) self.assertEqual(path, MANAGED_PATH) self.assertEqual(python_class, ManagedTwoInterface) self.assertIn('test_str', python_properties) self.assertIn('test_int', python_properties) with self.subTest('Parse added unknown'): with self.assertRaises(KeyError): path, python_class, python_properties = ( parse_interfaces_added( ManagedInterface, caught_added) ) with self.assertRaises(KeyError): path, python_class, python_properties = ( parse_interfaces_added( ManagedInterface, caught_added, on_unknown_interface='none', ) ) path, python_class, python_properties = ( parse_interfaces_added( ManagedInterface, caught_added, on_unknown_interface='none', on_unknown_member='reuse', ) ) self.assertEqual(path, MANAGED_PATH) self.assertIsNone(python_class) self.assertIn('TestStr', python_properties) self.assertIn('TestInt', python_properties) get_managed_data = ( await object_manager_connection.get_managed_objects() ) with self.subTest('Parse get managed objects class'): managed_dict = ( parse_get_managed_objects( ManagedTwoInterface, get_managed_data, ) ) self.assertIn(MANAGED_PATH, managed_dict) managed_class, managed_properties = ( managed_dict[MANAGED_PATH] ) self.assertEqual(managed_class, ManagedTwoInterface) self.assertIn('test_str', managed_properties) self.assertIn('test_int', managed_properties) with self.subTest('Parse get managed objects object'): managed_dict = ( parse_get_managed_objects( managed_object, get_managed_data, ) ) self.assertIn(MANAGED_PATH, managed_dict) managed_class, managed_properties = ( managed_dict[MANAGED_PATH] ) self.assertEqual(managed_class, ManagedTwoInterface) self.assertIn('test_str', managed_properties) self.assertIn('test_int', managed_properties) with self.subTest('Parse get managed objects iterable'): managed_dict = ( parse_get_managed_objects( (ManagedInterface, ManagedTwoInterface), get_managed_data, ) ) self.assertIn(MANAGED_PATH, managed_dict) managed_class, managed_properties = ( managed_dict[MANAGED_PATH] ) self.assertEqual(managed_class, ManagedTwoInterface) self.assertIn('test_str', managed_properties) self.assertIn('test_int', managed_properties) with self.subTest('Parse get managed objects unknown'): with self.assertRaises(KeyError): managed_dict = ( parse_get_managed_objects( ManagedInterface, get_managed_data, ) ) with self.assertRaises(KeyError): managed_dict = ( parse_get_managed_objects( ManagedInterface, get_managed_data, on_unknown_interface='none', ) ) managed_dict = ( parse_get_managed_objects( ManagedInterface, get_managed_data, on_unknown_interface='none', on_unknown_member='reuse', ) ) path, python_class, python_properties = ( parse_interfaces_added( ManagedInterface, caught_added, on_unknown_interface='none', on_unknown_member='reuse', ) ) self.assertIn(MANAGED_PATH, managed_dict) managed_class, managed_properties = ( managed_dict[MANAGED_PATH] ) self.assertIsNone(managed_class) self.assertIn('TestStr', managed_properties) self.assertIn('TestInt', managed_properties) async with self.assertDbusSignalEmits( object_manager_connection.interfaces_removed ) as removed_interfaces_catch: object_manager.remove_managed_object(managed_object) interfaces_removed_data = removed_interfaces_catch.output[0] with self.subTest('Parse removed class'): path, python_class = ( parse_interfaces_removed( ManagedTwoInterface, interfaces_removed_data, ) ) self.assertEqual(path, MANAGED_PATH) self.assertEqual(python_class, ManagedTwoInterface) with self.subTest('Parse removed unknown'): with self.assertRaises(KeyError): path, python_class = ( parse_interfaces_removed( ManagedInterface, interfaces_removed_data, ) ) path, python_class = ( parse_interfaces_removed( ManagedInterface, interfaces_removed_data, on_unknown_interface='none', ) ) self.assertEqual(path, MANAGED_PATH) self.assertIsNone(python_class) async def test_main_export_handle(self) -> None: await self.bus.request_name_async(CONNECTION_NAME, 0) object_manager = ObjectManagerTestInterface() object_manager_connection = ObjectManagerTestInterface.new_proxy( CONNECTION_NAME, OBJECT_MANAGER_PATH) with object_manager.export_to_dbus(OBJECT_MANAGER_PATH): self.assertIsInstance( await object_manager_connection.get_managed_objects(), dict, ) with self.assertRaises(DbusUnknownObjectError): self.assertIsInstance( await object_manager_connection.get_managed_objects(), dict, ) async def test_secondary_export_handle(self) -> None: await self.bus.request_name_async(CONNECTION_NAME, 0) object_manager = ObjectManagerTestInterface() object_manager_connection = ObjectManagerTestInterface.new_proxy( CONNECTION_NAME, OBJECT_MANAGER_PATH) object_manager.export_to_dbus(OBJECT_MANAGER_PATH) managed_object = ManagedInterface() managed_proxy = ManagedInterface.new_proxy( CONNECTION_NAME, MANAGED_PATH, ) async with self.assertDbusSignalEmits( object_manager_connection.interfaces_added ) as added, self.assertDbusSignalEmits( object_manager_connection.interfaces_removed ) as removed, object_manager.export_with_manager( MANAGED_PATH, managed_object, ): self.assertEqual( await managed_proxy.test_int, TEST_NUMBER, ) self.assertEqual(added.output[0][0], MANAGED_PATH) self.assertEqual(removed.output[0][0], MANAGED_PATH) with self.assertRaises(DbusUnknownObjectError): await managed_proxy.test_int python-sdbus-0.14.0/test/test_proxies.py000066400000000000000000000030361477456016000203300ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import main from sdbus.unittest import IsolatedDbusTestCase from sdbus_async.dbus_daemon import FreedesktopDbus class TestFreedesktopDbus(IsolatedDbusTestCase): async def test_connection(self) -> None: dbus_object = FreedesktopDbus(self.bus) await dbus_object.dbus_ping() await dbus_object.dbus_introspect() await dbus_object.dbus_machine_id() self.assertIsInstance(await dbus_object.get_id(), str) self.assertIsInstance(await dbus_object.features, list) with self.subTest('Check __name__'): self.assertEqual(FreedesktopDbus.__name__, 'FreedesktopDbus') if __name__ == "__main__": main() python-sdbus-0.14.0/test/test_read_write_dbus_types.py000066400000000000000000000314271477456016000232320ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import main from sdbus.sd_bus_internals import SdBus, SdBusMessage from sdbus.unittest import IsolatedDbusTestCase from sdbus import SdBusLibraryError def create_message(bus: SdBus) -> SdBusMessage: return bus.new_method_call_message( 'org.freedesktop.systemd1', '/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager', 'GetUnit') class TestDbusTypes(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() def test_unsigned(self) -> None: message = create_message(self.bus) int_t_max = 2**64 unsigned_integers = ((2**8)-1, (2**16)-1, (2**32)-1, int_t_max-1) message.append_data("yqut", *unsigned_integers) # Test overflows self.assertRaises( OverflowError, message.append_data, "y", 2**8) self.assertRaises( OverflowError, message.append_data, "q", 2**16) self.assertRaises( OverflowError, message.append_data, "u", 2**32) self.assertRaises( OverflowError, message.append_data, "t", 2**64) self.assertRaises( OverflowError, message.append_data, "y", -1) self.assertRaises( OverflowError, message.append_data, "q", -1) self.assertRaises( OverflowError, message.append_data, "u", -1) self.assertRaises( OverflowError, message.append_data, "t", -1) message.seal() return_integers = message.get_contents() self.assertEqual(unsigned_integers, return_integers) def test_signed(self) -> None: message = create_message(self.bus) int_n_max = (2**(16-1))-1 int_i_max = (2**(32-1))-1 int_x_max = (2**(64-1))-1 signed_integers_positive = (int_n_max, int_i_max, int_x_max) message.append_data("nix", *signed_integers_positive) self.assertRaises( OverflowError, message.append_data, "n", int_n_max + 1) self.assertRaises( OverflowError, message.append_data, "i", int_i_max + 1) self.assertRaises( OverflowError, message.append_data, "x", int_x_max + 1) int_n_min = -(2**(16-1)) int_i_min = -(2**(32-1)) int_x_min = -(2**(64-1)) signed_integers_negative = (int_n_min, int_i_min, int_x_min) message.append_data("n", int_n_min) self.assertRaises( OverflowError, message.append_data, "n", int_n_min - 1) message.append_data("i", int_i_min) self.assertRaises( OverflowError, message.append_data, "i", int_i_min - 1) message.append_data("x", int_x_min) self.assertRaises( OverflowError, message.append_data, "x", int_x_min - 1) message.seal() return_integers = message.get_contents() self.assertEqual(signed_integers_positive + signed_integers_negative, return_integers) def test_strings(self) -> None: message = create_message(self.bus) test_string = "test" test_path = "/test/object" test_signature = "say(xsai)o" message.append_data("sog", test_string, test_path, test_signature) message.seal() self.assertEqual(message.get_contents(), (test_string, test_path, test_signature)) def test_double(self) -> None: message = create_message(self.bus) test_double = 1.0 message.append_data("d", test_double) message.seal() self.assertEqual(message.get_contents(), test_double) def test_bool(self) -> None: message = create_message(self.bus) test_booleans = (True, False, False, True, 1 == 1) message.append_data("bbbbb", *test_booleans) self.assertRaises(TypeError, message.append_data, 'b', 'asdasad') message.seal() self.assertEqual(message.get_contents(), test_booleans) def test_array(self) -> None: message = create_message(self.bus) test_string_array = ["Ttest", "serawer", "asdadcxzc"] message.append_data("as", test_string_array) test_bytes_array = b"asdasrddjkmlh\ngnmflkdtgh\0oer27852y4785823" message.append_data("ay", test_bytes_array) test_int_list = [1234, 123123, 764523] message.append_data("ai", test_int_list) message.append_data("ax", []) self.assertRaises(TypeError, message.append_data, "a", test_int_list) message.seal() self.assertEqual( message.get_contents(), (test_string_array, test_bytes_array, test_int_list, [])) def test_empty_array(self) -> None: message = create_message(self.bus) test_array: list[str] = [] message.append_data("as", test_array) message.seal() self.assertEqual( message.get_contents(), test_array) def test_array_compound(self) -> None: message = create_message(self.bus) test_string_array = ["Ttest", "serawer", "asdadcxzc"] test_bytes_array = b"asdasrddjkmlh\ngnmflkdtgh\0oer27852y4785823" test_int_list = [1234, 123123, 764523] message.append_data( "asayai", test_string_array, test_bytes_array, test_int_list) message.seal() self.assertEqual(message.get_contents(), (test_string_array, test_bytes_array, test_int_list)) def test_nested_array(self) -> None: message = create_message(self.bus) test_string_array_one = ["Ttest", "serawer", "asdadcxzc"] test_string_array_two = ["asdaf", "seragdsfrgdswer", "sdfsdgg"] message.append_data( "aas", [test_string_array_one, test_string_array_two]) message.seal() self.assertEqual(message.get_contents(), [test_string_array_one, test_string_array_two]) def test_struct(self) -> None: message = create_message(self.bus) struct_data = (123123, "test") message.append_data("(xs)", struct_data) message.seal() self.assertEqual(message.get_contents(), struct_data) def test_dict(self) -> None: message = create_message(self.bus) test_dict = {'test': 'a', 'asdaefd': 'cvbcfg'} message.append_data("a{ss}", test_dict) self.assertRaises( TypeError, message.append_data, "{ss}", test_dict) message.seal() self.assertEqual(message.get_contents(), test_dict) def test_empty_dict(self) -> None: message = create_message(self.bus) test_dict: dict[str, str] = {} message.append_data("a{ss}", test_dict) message.seal() self.assertEqual(message.get_contents(), test_dict) def test_dict_nested_array(self) -> None: message = create_message(self.bus) test_array_one = [12, 1234234, 5, 2345, 24, 5623, 46, 2546, 68798] test_array_two = [124, 5, 356, 3, 57, 35, 67, 356, 2, 647, 36, 5784, 8, 809] test_dict = {'test': test_array_one, 'asdaefd': test_array_two} message.append_data("a{sax}", test_dict) message.seal() self.assertEqual(message.get_contents(), test_dict) def test_variant(self) -> None: message = create_message(self.bus) test_signature = "x" test_int = 1241354 message.append_data("v", (test_signature, test_int)) message.seal() self.assertEqual(message.get_contents(), (test_signature, test_int)) def test_array_of_variant(self) -> None: message = create_message(self.bus) test_signature_one = "(ssx)" test_str_1 = 'asdasdasd' test_str_2 = 'asdasdaddasdasdzc' test_int = 1241354 test_signature_two = "ai" test_array = [12, 1234234, 5, 2345, 24, 5623, 46, 2546, 68798] message.append_data( "av", [ (test_signature_one, (test_str_1, test_str_2, test_int)), (test_signature_two, test_array) ] ) message.seal() self.assertEqual( message.get_contents(), [ (test_signature_one, (test_str_1, test_str_2, test_int)), (test_signature_two, test_array) ] ) def test_array_of_dict(self) -> None: message = create_message(self.bus) test_data = [ {'asdasd': 'asdasd'}, {'asdasdsg': 'asdasfdaf'}, {}, ] message.append_data("aa{ss}", test_data) message.seal() self.assertEqual( message.get_contents(), test_data) def test_array_of_struct(self) -> None: message = create_message(self.bus) test_data = [ ('asdasd', 34636), ('asdasdads', -5425), ] message.append_data("a(si)", test_data) message.seal() self.assertEqual( message.get_contents(), test_data) def test_dict_of_struct(self) -> None: message = create_message(self.bus) test_dict = { 1: ('asdasd', 'xcvghtrh'), 2: ('rjhtyjdg', 'gbdtsret'), 3: ('gvsgdthgdeth', 'gsgderfgd'), } message.append_data("a{n(ss)}", test_dict) message.seal() self.assertEqual( message.get_contents(), test_dict) def test_struct_with_dict(self) -> None: message = create_message(self.bus) test_struct = ( 'asdasdag', { 'asdasd': 25, 'dfasf': 63 }, 542524, True ) message.append_data("(sa{sq}ib)", test_struct) message.seal() self.assertEqual( message.get_contents(), test_struct ) def test_dict_of_array(self) -> None: message = create_message(self.bus) test_dict = { '/': [341, 134, 764, 8986], '/test': [-245, -245, -1, 25], } message.append_data("a{oai}", test_dict) message.seal() self.assertEqual( message.get_contents(), test_dict ) def test_array_of_array(self) -> None: message = create_message(self.bus) test_array = [ ['asda', 'afgrfyhgdr', 'adffgvfdrfg'], ['afhryfjh', 'sgffgddrhg'], [] ] message.append_data("aas", test_array) message.seal() self.assertEqual( message.get_contents(), test_array ) def test_sealed_message_append(self) -> None: message = create_message(self.bus) message.append_data('s', 'test') message.seal() self.assertRaises(SdBusLibraryError, message.append_data, 's', 'error') def test_message_properties(self) -> None: message = create_message(self.bus) self.assertEqual(message.destination, 'org.freedesktop.systemd1') self.assertEqual(message.path, '/org/freedesktop/systemd1') self.assertEqual(message.interface, 'org.freedesktop.systemd1.Manager') self.assertEqual(message.member, 'GetUnit') self.assertIsNone(message.sender) def test_string_subclass(self) -> None: from enum import Enum class TestEnum(str, Enum): SOMETHING = 'test' message = create_message(self.bus) message.append_data('s', TestEnum.SOMETHING) message.seal() self.assertEqual(message.get_contents(), TestEnum.SOMETHING) def test_reading_multiple_times(self) -> None: message = create_message(self.bus) message.append_data('s', 'test') message.seal() for _ in range(5): self.assertEqual( message.get_contents(), "test", ) if __name__ == "__main__": main() python-sdbus-0.14.0/test/test_request_name.py000066400000000000000000000160661477456016000213360ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import get_running_loop, sleep, wait_for from unittest import main from sdbus.exceptions import ( SdBusLibraryError, SdBusRequestNameAlreadyOwnerError, SdBusRequestNameError, SdBusRequestNameExistsError, SdBusRequestNameInQueueError, ) from sdbus.sd_bus_internals import NameAllowReplacementFlag, NameQueueFlag from sdbus.unittest import IsolatedDbusTestCase from sdbus_async.dbus_daemon import FreedesktopDbus from sdbus import ( request_default_bus_name, request_default_bus_name_async, sd_bus_open_user, ) TEST_BUS_NAME = 'com.example.test' TEST_BUS_NAME_regex_match = TEST_BUS_NAME.replace('.', r'\.') class TestRequestNameLowLevel(IsolatedDbusTestCase): def test_request_name_exception_tree(self) -> None: # Test that SdBusRequestNameError is super class # of other request name exceptions self.assertTrue( issubclass( SdBusRequestNameAlreadyOwnerError, SdBusRequestNameError, ) ) self.assertTrue( issubclass( SdBusRequestNameExistsError, SdBusRequestNameError, ) ) self.assertTrue( issubclass( SdBusRequestNameInQueueError, SdBusRequestNameError, ) ) # Test the opposite self.assertFalse( issubclass( SdBusRequestNameAlreadyOwnerError, SdBusRequestNameExistsError, ) ) self.assertFalse( issubclass( SdBusRequestNameInQueueError, SdBusRequestNameExistsError, ) ) self.assertFalse( issubclass( SdBusRequestNameInQueueError, SdBusRequestNameAlreadyOwnerError, ) ) async def test_name_exists_async(self) -> None: extra_bus = sd_bus_open_user() await self.bus.request_name_async(TEST_BUS_NAME, 0) with self.assertRaises(SdBusRequestNameExistsError): await wait_for( extra_bus.request_name_async(TEST_BUS_NAME, 0), timeout=1, ) async def test_name_already_async(self) -> None: await self.bus.request_name_async(TEST_BUS_NAME, 0) with self.assertRaises(SdBusRequestNameAlreadyOwnerError): await wait_for( self.bus.request_name_async(TEST_BUS_NAME, 0), timeout=1, ) async def test_name_queued_async(self) -> None: extra_bus = sd_bus_open_user() await self.bus.request_name_async(TEST_BUS_NAME, 0) with self.assertRaises(SdBusRequestNameInQueueError): await wait_for( extra_bus.request_name_async(TEST_BUS_NAME, NameQueueFlag), timeout=1, ) async def test_name_other_error_async(self) -> None: extra_bus = sd_bus_open_user() extra_bus.close() with self.assertRaises(SdBusLibraryError): await wait_for( extra_bus.request_name_async(TEST_BUS_NAME, 0), timeout=1, ) def test_name_exists_block(self) -> None: extra_bus = sd_bus_open_user() self.bus.request_name(TEST_BUS_NAME, 0) with self.assertRaisesRegex( SdBusRequestNameExistsError, TEST_BUS_NAME_regex_match, ): extra_bus.request_name(TEST_BUS_NAME, 0) def test_name_already_block(self) -> None: self.bus.request_name(TEST_BUS_NAME, 0) with self.assertRaisesRegex( SdBusRequestNameAlreadyOwnerError, TEST_BUS_NAME_regex_match, ): self.bus.request_name(TEST_BUS_NAME, 0) def test_name_queued_block(self) -> None: extra_bus = sd_bus_open_user() self.bus.request_name(TEST_BUS_NAME, 0) with self.assertRaisesRegex( SdBusRequestNameInQueueError, TEST_BUS_NAME_regex_match, ): extra_bus.request_name(TEST_BUS_NAME, NameQueueFlag) def test_name_other_error_block(self) -> None: extra_bus = sd_bus_open_user() extra_bus.close() with self.assertRaises(SdBusLibraryError): extra_bus.request_name(TEST_BUS_NAME, 0) class TestRequestNameBlock(IsolatedDbusTestCase): def test_request_name_replacement(self) -> None: extra_bus = sd_bus_open_user() extra_bus.request_name(TEST_BUS_NAME, NameAllowReplacementFlag) with self.assertRaises(SdBusRequestNameExistsError): request_default_bus_name(TEST_BUS_NAME) request_default_bus_name( TEST_BUS_NAME, replace_existing=True, ) class TestRequestNameAsync(IsolatedDbusTestCase): async def test_request_name_replacement(self) -> None: extra_bus = sd_bus_open_user() await extra_bus.request_name_async( TEST_BUS_NAME, NameAllowReplacementFlag, ) with self.assertRaises(SdBusRequestNameExistsError): await request_default_bus_name_async(TEST_BUS_NAME) await request_default_bus_name_async( TEST_BUS_NAME, replace_existing=True, ) async def test_request_name_queue(self) -> None: extra_bus = sd_bus_open_user() await extra_bus.request_name_async(TEST_BUS_NAME, 0) with self.assertRaises(SdBusRequestNameInQueueError): await request_default_bus_name_async( TEST_BUS_NAME, queue=True, ) async def catch_owner_changed() -> str: dbus = FreedesktopDbus() async for name, old, new in dbus.name_owner_changed: if name != TEST_BUS_NAME: continue if old and new: return new raise RuntimeError loop = get_running_loop() owner_changed_task = loop.create_task(catch_owner_changed()) await sleep(0) extra_bus.close() await wait_for(owner_changed_task, timeout=0.5) with self.assertRaises(SdBusRequestNameAlreadyOwnerError): await request_default_bus_name_async(TEST_BUS_NAME) if __name__ == '__main__': main() python-sdbus-0.14.0/test/test_sdbus_async.py000066400000000000000000001002241477456016000211510ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from asyncio import Event, get_running_loop from asyncio import run as asyncio_run from asyncio import sleep, wait_for from asyncio.subprocess import create_subprocess_exec from typing import TYPE_CHECKING from unittest import SkipTest from sdbus.exceptions import ( DbusFailedError, DbusFileExistsError, DbusNoReplyError, DbusPropertyReadOnlyError, DbusUnknownObjectError, SdBusLibraryError, SdBusUnmappedMessageError, ) from sdbus.sd_bus_internals import ( DBUS_ERROR_TO_EXCEPTION, DbusPropertyEmitsChangeFlag, ) from sdbus.unittest import IsolatedDbusTestCase from sdbus.utils.parse import parse_properties_changed from sdbus import ( DbusInterfaceCommonAsync, DbusNoReplyFlag, dbus_method_async, dbus_method_async_override, dbus_property_async, dbus_property_async_override, dbus_signal_async, get_current_message, ) if TYPE_CHECKING: from sdbus.dbus_proxy_async_interfaces import ( DBUS_PROPERTIES_CHANGED_TYPING, ) else: DBUS_PROPERTIES_CHANGED_TYPING = None class TestPing(IsolatedDbusTestCase): async def test_ping_with_busctl(self) -> None: try: busctl_process = await create_subprocess_exec( '/usr/bin/busctl', '--user', 'call', 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus.Peer', 'Ping', ) except FileNotFoundError: raise SkipTest('busctl not installed') return_code = await busctl_process.wait() self.assertEqual(return_code, 0) async def test_ping(self) -> None: m = self.bus.new_method_call_message( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus.Peer', 'Ping', ) r = await self.bus.call_async(m) self.assertIsNone(r.get_contents()) class TestRequestName(IsolatedDbusTestCase): async def test_request_name(self) -> None: await self.bus.request_name_async("org.example.test", 0) TEST_INTERFACE_NAME = "org.test.test" class TestInterface( DbusInterfaceCommonAsync, interface_name=TEST_INTERFACE_NAME, ): def __init__(self) -> None: super().__init__() self.test_string = 'test_property' self.test_string_read = 'read' self.test_no_reply_string = 'no' self.property_private = 100 self.no_reply_sync = Event() @dbus_method_async("s", "s") async def upper(self, string: str) -> str: """Uppercase the input""" return string.upper() @dbus_method_async(result_signature='s') async def get_sender(self) -> str: message = get_current_message() return message.sender or '' @dbus_method_async(result_signature='x') async def test_int(self) -> int: return 1 @dbus_method_async(result_signature='x', result_args_names=('an_int', )) async def int_annotated(self) -> int: return 1 @dbus_property_async( "s", flags=DbusPropertyEmitsChangeFlag, ) def test_property(self) -> str: """Test property""" return self.test_string @test_property.setter def test_property_set(self, new_property: str) -> None: self.test_string = new_property @dbus_property_async("s") def test_property_read_only(self) -> str: return self.test_string_read @dbus_property_async("x") def test_property_private(self) -> int: return self.property_private @test_property_private.setter_private def test_private_setter(self, new_value: int) -> None: self.property_private = new_value @dbus_method_async("sb", "s") async def kwargs_function( self, input: str = 'test', is_upper: bool = True) -> str: if is_upper: return input.upper() else: return input.lower() @dbus_method_async("sb", "s", 0, ('string_result', )) async def kwargs_function_annotated( self, input: str = 'test', is_upper: bool = True) -> str: if is_upper: return input.upper() else: return input.lower() @dbus_signal_async('ss') def test_signal(self) -> tuple[str, str]: """Test signal""" raise NotImplementedError @dbus_method_async() async def raise_base_exception(self) -> None: raise DbusFailedError('Test error') @dbus_method_async() async def raise_derived_exception(self) -> None: raise DbusFileExistsError('Test error 2') @dbus_method_async() async def raise_custom_error(self) -> None: raise DbusErrorTest('Custom') @dbus_method_async() async def raise_and_unmap_error(self) -> None: try: DBUS_ERROR_TO_EXCEPTION.pop('org.example.Nothing') except KeyError: ... raise DbusErrorUnmappedLater('Should be unmapped') @dbus_method_async() async def raise_python_exc(self) -> None: raise ValueError("Test!") @dbus_method_async('s', flags=DbusNoReplyFlag) async def no_reply_method(self, new_value: str) -> None: self.no_reply_sync.set() @dbus_property_async("s") def test_constant_property(self) -> str: return "a" @dbus_method_async( result_signature='(ss)' ) async def test_struct_return(self) -> tuple[str, str]: return ('hello', 'world') @dbus_method_async( result_signature='(ss)' ) async def test_struct_return_workaround(self) -> tuple[tuple[str, str]]: return (('hello', 'world'), ) @dbus_method_async() async def looong_method(self) -> None: await sleep(100) @dbus_signal_async() def empty_signal(self) -> None: raise NotImplementedError @dbus_method_async() async def returns_none_method(self) -> None: return @dbus_method_async( input_signature="(iiii)", result_signature="i", ) async def takes_struct_method( self, int_struct: tuple[int, int, int, int], ) -> int: a, b, c, d = int_struct return a*b*c*d @dbus_method_async("s", "x") async def return_length(self, input_str: str) -> int: return len(input_str) class DbusErrorTest(DbusFailedError): dbus_error_name = 'org.example.Error' class DbusErrorUnmappedLater(DbusFailedError): dbus_error_name = 'org.example.Nothing' TEST_SERVICE_NAME = 'org.example.test' def initialize_object() -> tuple[TestInterface, TestInterface]: test_object = TestInterface() test_object.export_to_dbus('/') test_object_connection = TestInterface.new_proxy( TEST_SERVICE_NAME, '/') return test_object, test_object_connection class TestProxy(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() await self.bus.request_name_async(TEST_SERVICE_NAME, 0) async def test_method_kwargs(self) -> None: test_object, test_object_connection = initialize_object() self.assertEqual( 'TEST', await test_object_connection.kwargs_function( 'test', True) ) self.assertEqual( 'TEST', await test_object_connection.kwargs_function()) self.assertEqual( 'test', await test_object_connection.kwargs_function( is_upper=False)) self.assertEqual( 'ASD', await test_object_connection.kwargs_function('asd')) self.assertEqual( 'ASD', await test_object_connection.kwargs_function(input='asd')) self.assertEqual( 'asd', await test_object_connection.kwargs_function( 'ASD', is_upper=False)) self.assertEqual( 'asd', await test_object_connection.kwargs_function( input='ASD', is_upper=False)) async def test_method(self) -> None: test_object, test_object_connection = initialize_object() test_string = 'asdafsrfgdrtuhrytuj' self.assertEqual(test_string.upper(), await test_object.upper(test_string)) self.assertEqual(1, await test_object_connection.test_int()) self.assertEqual( test_string.upper(), await test_object_connection.upper(test_string)) self.assertEqual( await wait_for(test_object.test_struct_return(), 0.5), await wait_for(test_object_connection.test_struct_return(), 0.5), ) self.assertEqual( (await wait_for( test_object.test_struct_return_workaround(), 0.5))[0], await wait_for( test_object_connection.test_struct_return_workaround(), 0.5), ) self.assertTrue(await test_object_connection.get_sender()) with self.subTest("Test method that returns None"): self.assertIsNone( await test_object .returns_none_method() # type: ignore[func-returns-value] ) self.assertIsNone( await test_object_connection .returns_none_method() # type: ignore[func-returns-value] ) with self.subTest("Test method that takes a single struct"): self.assertEqual( await test_object.takes_struct_method((2, 3, 4, 5)), 120, ) self.assertEqual( await test_object_connection.takes_struct_method((9, 8, 7, 6)), 3024, ) async def test_subclass(self) -> None: test_object, test_object_connection = initialize_object() test_var = ['asdasd'] class TestInheritence(TestInterface): @dbus_method_async_override() async def test_int(self) -> int: return 2 @dbus_property_async_override() def test_property(self) -> str: return test_var[0] @test_property.setter def test_property_setter(self, var: str) -> None: test_var.insert(0, var) test_subclass = TestInheritence() test_subclass.export_to_dbus('/subclass', self.bus) self.assertEqual(await test_subclass.test_int(), 2) test_subclass_connection = TestInheritence.new_proxy( TEST_SERVICE_NAME, '/subclass', self.bus) self.assertEqual(await test_subclass_connection.test_int(), 2) self.assertEqual(test_var[0], await test_subclass.test_property) await test_subclass.test_property.set_async('12345') self.assertEqual(test_var[0], await test_subclass.test_property) self.assertEqual('12345', await test_subclass.test_property) with self.subTest('Test dbus to python mapping'): dbus_elements_map = ( { interface_name: meta.dbus_member_to_python_attr for interface_name, meta in TestInterface._dbus_iter_interfaces_meta() } ) self.assertIn( "TestInt", dbus_elements_map[TEST_INTERFACE_NAME], ) self.assertIn( "TestInt", dbus_elements_map[TEST_INTERFACE_NAME], ) self.assertIn( "TestProperty", dbus_elements_map[TEST_INTERFACE_NAME], ) with self.subTest('Tripple subclass'): class TestInheritenceTri(TestInheritence): @dbus_method_async_override() async def test_int(self) -> int: return 3 @dbus_property_async_override() def test_property(self) -> str: return 'tri' test_subclass_tri = TestInheritenceTri() test_subclass_tri.export_to_dbus('/subclass/tri', self.bus) self.assertEqual(await test_subclass_tri.test_int(), 3) test_subclass_tri_connection = TestInheritenceTri.new_proxy( TEST_SERVICE_NAME, '/subclass/tri', self.bus) self.assertEqual(await test_subclass_tri_connection.test_int(), 3) self.assertEqual(await test_subclass_tri.test_property, 'tri') self.assertEqual( await test_subclass_tri_connection.test_property, 'tri') async def test_properties(self) -> None: test_object, test_object_connection = initialize_object() self.assertEqual( 'test_property', await test_object.test_property.get_async()) self.assertEqual( 'test_property', await test_object.test_property) self.assertEqual( await wait_for(test_object_connection.test_property, 0.5), await test_object.test_property) self.assertEqual( 'test_property', await wait_for(test_object_connection.test_property, 0.5)) self.assertEqual( await test_object.test_property_read_only, await wait_for( test_object_connection.test_property_read_only, 0.5)) new_string = 'asdsgrghdthdth' await wait_for( test_object_connection.test_property.set_async( new_string), 0.5) self.assertEqual( new_string, await test_object.test_property) self.assertEqual( new_string, await wait_for(test_object_connection.test_property, 0.5) ) async def test_signal(self) -> None: test_object, test_object_connection = initialize_object() test_tuple = ('sgfsretg', 'asd') async with self.assertDbusSignalEmits( test_object.test_signal ) as local_signals_record, self.assertDbusSignalEmits( test_object_connection.test_signal ) as remote_signals_record: test_object.test_signal.emit(test_tuple) async with self.assertDbusSignalEmits( test_object.test_signal ) as local_signals_record, self.assertDbusSignalEmits( test_object_connection.test_signal ) as remote_signals_record: test_object.test_signal.emit(test_tuple) self.assertEqual([test_tuple], local_signals_record.output) self.assertEqual([test_tuple], remote_signals_record.output) async def test_signal_catch_anywhere(self) -> None: test_object, test_object_connection = initialize_object() loop = get_running_loop() test_tuple = ('sgfsretg', 'asd') with self.subTest('Catch anywhere over D-Bus object'): async def catch_anywhere_oneshot_dbus( ) -> tuple[str, tuple[str, str]]: async for x in test_object_connection.test_signal\ .catch_anywhere(): return x raise RuntimeError catch_anywhere_over_dbus_task \ = loop.create_task(catch_anywhere_oneshot_dbus()) await sleep(0) test_object.test_signal.emit(test_tuple) self.assertEqual( ('/', test_tuple), await wait_for(catch_anywhere_over_dbus_task, timeout=1), ) with self.subTest('Catch anywhere over D-Bus class'): async def catch_anywhere_oneshot_from_class( ) -> tuple[str, tuple[str, str]]: async for x in TestInterface.test_signal.catch_anywhere( TEST_SERVICE_NAME, self.bus): return x raise RuntimeError catch_anywhere_from_class_task \ = loop.create_task(catch_anywhere_oneshot_from_class()) await sleep(0) test_object.test_signal.emit(test_tuple) self.assertEqual( ('/', test_tuple), await wait_for(catch_anywhere_from_class_task, timeout=1), ) with self.subTest('Catch anywhere over local object'): async def catch_anywhere_oneshot_local( ) -> tuple[str, tuple[str, str]]: async for x in test_object.test_signal.catch_anywhere(): return x raise RuntimeError catch_anywhere_over_local_task \ = loop.create_task(catch_anywhere_oneshot_local()) with self.assertRaises(NotImplementedError): await wait_for( catch_anywhere_over_local_task, timeout=1, ) async def test_signal_multiple_readers(self) -> None: test_object, test_object_connection = initialize_object() loop = get_running_loop() test_tuple = ('sgfsretg', 'asd') async def reader_one() -> tuple[str, str]: async for x in test_object_connection.test_signal.catch(): return test_tuple raise RuntimeError async def reader_two() -> tuple[str, str]: async for x in test_object_connection.test_signal.catch(): return test_tuple raise RuntimeError t1 = loop.create_task(reader_one()) t2 = loop.create_task(reader_two()) loop.call_at(0, test_object.test_signal.emit, test_tuple) self.assertEqual(test_tuple, await wait_for(t1, timeout=1)) self.assertEqual(test_tuple, await wait_for(t2, timeout=1)) async def test_exceptions(self) -> None: test_object, test_object_connection = initialize_object() async def first_test() -> None: await test_object_connection.raise_base_exception() with self.assertRaises(DbusFailedError): await wait_for(first_test(), timeout=1) async def second_test() -> None: await test_object_connection.raise_derived_exception() with self.assertRaises(DbusFileExistsError): await wait_for(second_test(), timeout=1) async def third_test() -> None: await test_object_connection.raise_custom_error() with self.assertRaises(DbusErrorTest): await wait_for(third_test(), timeout=1) def bad_exception_error_name_used() -> None: class BadDbusError(DbusFailedError): dbus_error_name = 'org.freedesktop.DBus.Error.NoMemory' self.assertRaises(ValueError, bad_exception_error_name_used) def bad_exception_no_error_name() -> None: class BadDbusError(DbusFailedError): ... self.assertRaises(TypeError, bad_exception_no_error_name) async def test_unmapped_expection() -> None: await test_object_connection.raise_and_unmap_error() with self.assertRaises(SdBusUnmappedMessageError): await wait_for(test_unmapped_expection(), timeout=1) async def test_no_reply_method(self) -> None: test_object, test_object_connection = initialize_object() await wait_for(test_object_connection.no_reply_method('yes'), timeout=1) await wait_for(test_object.no_reply_sync.wait(), timeout=1) async def test_interface_remove(self) -> None: test_object, test_object_connection = initialize_object() from gc import collect del test_object collect() with self.assertRaises(DbusUnknownObjectError): await wait_for(test_object_connection.dbus_introspect(), timeout=0.2) def test_docstring(self) -> None: test_object, test_object_connection = initialize_object() from pydoc import getdoc self.assertTrue(getdoc(test_object.upper)) self.assertTrue(getdoc(test_object_connection.upper)) self.assertTrue(getdoc(test_object.test_property)) self.assertTrue( getdoc(test_object_connection.test_property)) self.assertTrue(getdoc(test_object.test_signal)) self.assertTrue( getdoc(test_object_connection.test_signal)) async def test_emits_properties_changed(self) -> None: test_object, test_object_connection = initialize_object() test_str = 'should_be_emited' loop = get_running_loop() async def catch_property_emit_connection() -> str: async for x in test_object_connection.properties_changed.catch(): for v in x[1].values(): probably_str = v[1] if isinstance(probably_str, str): return probably_str else: raise TypeError raise ValueError async def catch_property_emit_local() -> str: async for x in test_object.properties_changed.catch(): for v in x[1].values(): probably_str = v[1] if isinstance(probably_str, str): return probably_str else: raise TypeError raise ValueError t1 = loop.create_task(catch_property_emit_connection()) t2 = loop.create_task(catch_property_emit_local()) await test_object_connection.test_property.set_async(test_str) t1_result = await wait_for(t1, timeout=0.2) t2_result = await wait_for(t2, timeout=0.2) self.assertEqual(t1_result, test_str) self.assertEqual(t2_result, test_str) async def test_bus_close(self) -> None: test_object, test_object_connection = initialize_object() async def too_long_wait() -> None: await test_object_connection.looong_method() self.bus.close() with self.assertRaises(SdBusLibraryError): await wait_for(too_long_wait(), timeout=1) async def test_bus_timerfd(self) -> None: test_object, test_object_connection = initialize_object() self.bus.method_call_timeout_usec = 10_000 # 0.01 seconds loop = get_running_loop() start = loop.time() with self.assertRaises(DbusNoReplyError): await wait_for(test_object_connection.looong_method(), timeout=1) self.assertAlmostEqual(loop.time() - start, 0.01, delta=0.01) async def test_signal_queue_wildcard_match(self) -> None: test_object, test_object_connection = initialize_object() loop = get_running_loop() future = loop.create_future() slot = await self.bus.match_signal_async( TEST_SERVICE_NAME, None, None, None, future.set_result) try: test_object.test_signal.emit(('test', 'signal')) await wait_for(future, timeout=1) message = future.result() self.assertEqual(message.member, "TestSignal") finally: slot.close() async def test_class_with_string_subclass_parameter(self) -> None: from enum import Enum class InterfaceNameEnum(str, Enum): FOO = 'org.example.foo' BAR = 'org.example.bar' class ObjectPathEnum(str, Enum): FOO = '/foo' BAR = '/bar' class EnumedInterfaceAsync( DbusInterfaceCommonAsync, interface_name=InterfaceNameEnum.BAR, ): @dbus_property_async('s') def hello_world(self) -> str: return 'Hello World!' test_object = EnumedInterfaceAsync() test_object.export_to_dbus(ObjectPathEnum.FOO) async def test_properties_get_all_dict(self) -> None: test_object, test_object_connection = initialize_object() dbus_dict = await test_object_connection._properties_get_all( TEST_INTERFACE_NAME) self.assertEqual( await test_object.test_property, dbus_dict['TestProperty'][1], ) self.assertEqual( await test_object.test_property, ( await test_object_connection.properties_get_all_dict() )['test_property'], ) async def test_empty_signal(self) -> None: test_object, test_object_connection = initialize_object() async with self.assertDbusSignalEmits( test_object.empty_signal ) as local_signals_record, self.assertDbusSignalEmits( test_object_connection.empty_signal ) as remote_signals_record: test_object.empty_signal.emit(None) self.assertEqual([None], local_signals_record.output) self.assertEqual([None], remote_signals_record.output) async def test_properties_changed(self) -> None: test_object, test_object_connection = initialize_object() test_str = 'should_be_emited' async with self.assertDbusSignalEmits( test_object_connection.properties_changed ) as properties_changed_catch: await test_object_connection.test_property.set_async(test_str) properties_changed_data = properties_changed_catch.output[0] parsed_dict_from_class = parse_properties_changed( TestInterface, properties_changed_data) self.assertEqual( test_str, parsed_dict_from_class['test_property'], ) parsed_dict_from_object = parse_properties_changed( test_object_connection, properties_changed_data) self.assertEqual( test_str, parsed_dict_from_object['test_property'], ) properties_changed_data[2].append('invalidated_property') parsed_dict_with_invalidation = parse_properties_changed( test_object, properties_changed_data, on_unknown_member='reuse', ) self.assertIsNone( parsed_dict_with_invalidation['invalidated_property']) async def test_property_private_setter(self) -> None: test_object, test_object_connection = initialize_object() new_value = 200 self.assertNotEqual( await test_object_connection.test_property_private, new_value ) with self.assertRaises(DbusPropertyReadOnlyError): await test_object_connection.test_property_private.set_async( new_value) async with self.assertDbusSignalEmits( test_object_connection.properties_changed ) as properties_changed_catch: await test_object.test_property_private.set_async(new_value) changed_properties = properties_changed_catch.output[0] self.assertEqual( await test_object_connection.test_property_private, new_value ) self.assertIn('TestPropertyPrivate', changed_properties[1]) async def test_property_override_setter_private(self) -> None: test_int = 1 class TestInterfacePrivateSetter(TestInterface): @dbus_property_async_override() def test_property_private(self) -> int: return test_int @test_property_private.setter_private def _private_setter(self, new_value: int) -> None: nonlocal test_int test_int = new_value test_object = TestInterfacePrivateSetter() test_object.export_to_dbus('/') test_object_connection = TestInterface.new_proxy( TEST_SERVICE_NAME, '/') self.assertEqual( await test_object_connection.test_property_private, test_int, ) async def catch_properties_changed() -> int: async for x in test_object_connection.properties_changed: changed_attr = parse_properties_changed( TestInterface, x)["test_property_private"] if not isinstance(changed_attr, int): raise TypeError return changed_attr raise RuntimeError catch_changed_task = get_running_loop( ).create_task(catch_properties_changed()) with self.assertRaises(DbusPropertyReadOnlyError): await test_object_connection.test_property_private.set_async(10) await test_object.test_property_private.set_async(10) self.assertEqual( await test_object_connection.test_property_private, 10, ) self.assertEqual( await test_object.test_property_private, test_int, ) self.assertEqual( await wait_for(catch_changed_task, timeout=1), 10, ) async def test_interface_composition(self) -> None: class OneInterface( DbusInterfaceCommonAsync, interface_name="org.example.one", ): @dbus_method_async(result_signature="x") async def one(self) -> int: raise NotImplementedError class TwoInterface( DbusInterfaceCommonAsync, interface_name="org.example.two", ): @dbus_method_async(result_signature="t") async def two(self) -> int: return 2 class CombinedInterface(OneInterface, TwoInterface): ... async def test_extremely_large_string(self) -> None: test_object, test_object_connection = initialize_object() extremely_large_string = "a" * 8423681 remote_len = await wait_for( test_object_connection.return_length( extremely_large_string ), timeout=10, ) self.assertEqual( remote_len, len(extremely_large_string), ) # Check that calling regular methods still works. for _ in range(5): await test_object_connection.returns_none_method() async def test_export_handle(self) -> None: test_object = TestInterface() test_object_connection = TestInterface.new_proxy( TEST_SERVICE_NAME, '/', ) with self.assertRaises(DbusUnknownObjectError): await test_object_connection.returns_none_method() with test_object.export_to_dbus("/"): await test_object_connection.returns_none_method() with self.assertRaises(DbusUnknownObjectError): await test_object_connection.returns_none_method() test_object2 = TestInterface() handle = test_object2.export_to_dbus("/") await test_object_connection.returns_none_method() handle.stop() with self.assertRaises(DbusUnknownObjectError): await test_object_connection.returns_none_method() def test_asyncio_run_different_loops(self) -> None: bus = self.bus async def test() -> None: dbus_object = DbusInterfaceCommonAsync.new_proxy( "org.freedesktop.DBus", "/org/freedesktop/DBus", bus, ) await wait_for(dbus_object.dbus_ping(), timeout=1) with self.assertRaisesRegex(RuntimeError, "different loop"): asyncio_run(test()) async def test_python_exc(self) -> None: test_object, test_object_connection = initialize_object() with self.assertRaisesRegex(ValueError, "Test!"): await test_object.raise_python_exc() with self.assertRaisesRegex(ValueError, "Test!"): await test_object_connection.raise_python_exc() async def test_empty_dbus_interface(self) -> None: class Empty( DbusInterfaceCommonAsync, interface_name="org.empty", ): ... empty_local = Empty() empty_local.export_to_dbus("/") empty_proxy = Empty.new_proxy(TEST_SERVICE_NAME, "/") intro = await empty_proxy.dbus_introspect() self.assertIn('', intro) python-sdbus-0.14.0/test/test_sdbus_async_bad_class.py000066400000000000000000000164131477456016000231520ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from gc import collect from unittest import TestCase from unittest import main as unittest_main from sdbus.dbus_common_funcs import PROPERTY_FLAGS_MASK, count_bits from sdbus import ( DbusDeprecatedFlag, DbusInterfaceCommonAsync, DbusPropertyConstFlag, DbusPropertyEmitsChangeFlag, dbus_method_async, dbus_method_async_override, dbus_property_async, dbus_signal_async, ) from .common_test_util import skip_if_no_asserts, skip_if_no_name_validations class TestInterface( DbusInterfaceCommonAsync, interface_name="org.example.good", ): @dbus_method_async(result_signature="i") async def test_int(self) -> int: return 1 class TestBadAsyncDbusClass(TestCase): def test_name_validations(self) -> None: skip_if_no_name_validations() with self.assertRaisesRegex( AssertionError, "^Invalid interface name", ): class BadInterfaceName( DbusInterfaceCommonAsync, interface_name="0.test", ): ... with self.assertRaisesRegex( AssertionError, "^Invalid method name", ): class BadMethodName( DbusInterfaceCommonAsync, interface_name="org.example", ): @dbus_method_async( result_signature="s", method_name="🤫", ) async def test(self) -> str: return "test" with self.assertRaisesRegex( AssertionError, "^Invalid property name", ): class BadPropertyName( DbusInterfaceCommonAsync, interface_name="org.example", ): @dbus_property_async( property_signature="s", property_name="🤫", ) def test(self) -> str: return "test" with self.assertRaisesRegex( AssertionError, "^Invalid signal name", ): class BadSignalName( DbusInterfaceCommonAsync, interface_name="org.example", ): @dbus_signal_async( signal_signature="s", signal_name="🤫", ) def test(self) -> str: raise NotImplementedError def test_property_flags(self) -> None: self.assertEqual(0, PROPERTY_FLAGS_MASK & DbusDeprecatedFlag) self.assertEqual( 1, count_bits( PROPERTY_FLAGS_MASK & (DbusDeprecatedFlag | DbusPropertyEmitsChangeFlag) ), ) self.assertEqual( 2, count_bits( PROPERTY_FLAGS_MASK & ( DbusDeprecatedFlag | DbusPropertyConstFlag | DbusPropertyEmitsChangeFlag ) ), ) with self.subTest("Test incorrect flags"), self.assertRaisesRegex( AssertionError, "^Incorrect number of Property flags", ): skip_if_no_asserts() class InvalidPropertiesFlags( DbusInterfaceCommonAsync, interface_name="org.test.invalidprop" ): @dbus_property_async( "s", flags=DbusPropertyConstFlag | DbusPropertyEmitsChangeFlag, ) def test_constant(self) -> str: return "a" with self.subTest("Valid properties flags"): class ValidPropertiesFlags( DbusInterfaceCommonAsync, interface_name="org.test.validprop" ): @dbus_property_async( "s", flags=DbusDeprecatedFlag | DbusPropertyEmitsChangeFlag, ) def test_constant(self) -> str: return "a" def test_bad_subclass(self) -> None: with self.assertRaises(ValueError): class TestInheritence(TestInterface): async def test_int(self) -> int: return 2 with self.assertRaises(ValueError): class TestInheritence2(TestInterface): @dbus_method_async_override() async def test_unrelated(self) -> int: return 2 def test_dbus_elements_without_interface_name(self) -> None: with self.assertRaisesRegex(TypeError, "without interface name"): class NoInterfaceName(DbusInterfaceCommonAsync): @dbus_method_async() async def example(self) -> None: ... def test_dbus_elements_without_interface_name_subclass(self) -> None: with self.assertRaisesRegex(TypeError, "without interface name"): class NoInterfaceName(TestInterface): @dbus_method_async() async def example(self) -> None: ... def test_shared_parent_class(self) -> None: class One(TestInterface): ... class Two(TestInterface): ... class Shared(One, Two): ... def test_combined_collision(self) -> None: class One( DbusInterfaceCommonAsync, interface_name="org.example.foo", ): @dbus_method_async() async def example(self) -> None: ... class Two( DbusInterfaceCommonAsync, interface_name="org.example.bar", ): @dbus_method_async() async def example(self) -> None: ... with self.assertRaisesRegex(ValueError, "collision"): class Combined(One, Two): ... def test_class_cleanup(self) -> None: class One( DbusInterfaceCommonAsync, interface_name="org.example.foo1", ): ... with self.assertRaises(ValueError): class Two( DbusInterfaceCommonAsync, interface_name="org.example.foo1", ): ... del One collect() # Let weak refs be processed class After( DbusInterfaceCommonAsync, interface_name="org.example.foo1", ): ... if __name__ == "__main__": unittest_main() python-sdbus-0.14.0/test/test_sdbus_async_introspection.py000066400000000000000000000110451477456016000241330ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from sdbus.unittest import IsolatedDbusTestCase from sdbus import DbusInterfaceCommonAsync, dbus_method_async TEST_SERVICE_NAME = 'org.example.test' def initialize_object( interface_class: type[DbusInterfaceCommonAsync], ) -> tuple[DbusInterfaceCommonAsync, DbusInterfaceCommonAsync]: test_object = interface_class() test_object.export_to_dbus('/') test_object_connection = interface_class.new_proxy( TEST_SERVICE_NAME, '/') return test_object, test_object_connection class TestIntrospection(IsolatedDbusTestCase): async def asyncSetUp(self) -> None: await super().asyncSetUp() await self.bus.request_name_async("org.example.test", 0) async def test_method_arg_names_none(self) -> None: class TestInterface( DbusInterfaceCommonAsync, interface_name="org.test.intro1", ): @dbus_method_async( input_signature="ss", result_signature="i", ) async def login( self, user_name: str, pin_code: str, ) -> int: return 0 obj, rem = initialize_object(TestInterface) introspection = await rem.dbus_introspect() self.assertNotIn('name="user_name"', introspection) self.assertNotIn('name="result"', introspection) self.assertNotIn('name="pin_code"', introspection) async def test_method_arg_names_result_names_only(self) -> None: class TestInterface( DbusInterfaceCommonAsync, interface_name="org.test.intro2", ): @dbus_method_async( input_signature="ss", result_signature="i", result_args_names=("result",) ) async def login( self, user_name: str, pin_code: str, ) -> int: return 0 obj, rem = initialize_object(TestInterface) introspection = await rem.dbus_introspect() self.assertIn('name="user_name"', introspection) self.assertIn('name="result"', introspection) self.assertIn('name="pin_code"', introspection) async def test_method_arg_names_full(self) -> None: class TestInterface( DbusInterfaceCommonAsync, interface_name="org.test.intro3", ): @dbus_method_async( input_signature="ss", input_args_names=("UserName", "PinCode"), result_signature="i", result_args_names=("Result",) ) async def login( self, user_name: str, pin_code: str, ) -> int: return 0 obj, rem = initialize_object(TestInterface) introspection = await rem.dbus_introspect() self.assertIn('name="UserName"', introspection) self.assertIn('name="Result"', introspection) self.assertIn('name="PinCode"', introspection) async def test_method_arg_names_no_return_args(self) -> None: class TestInterface( DbusInterfaceCommonAsync, interface_name="org.test.intro4", ): @dbus_method_async( input_signature="ss", result_args_names=(), ) async def login( self, user_name: str, pin_code: str, ) -> None: return None obj, rem = initialize_object(TestInterface) introspection = await rem.dbus_introspect() self.assertIn('name="user_name"', introspection) self.assertIn('name="pin_code"', introspection) python-sdbus-0.14.0/test/test_sdbus_block.py000066400000000000000000000063011477456016000211270ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import main from sdbus.exceptions import DbusPropertyReadOnlyError from sdbus.unittest import IsolatedDbusTestCase from sdbus_block.dbus_daemon import FreedesktopDbus from sdbus import DbusInterfaceCommon, dbus_method class TestSync(IsolatedDbusTestCase): def test_sync(self) -> None: self.bus.request_name('org.example.test', 0) s = FreedesktopDbus(self.bus) s.dbus_ping() s.dbus_introspect() s.dbus_machine_id() self.assertIsInstance(s.get_id(), str) self.assertIsInstance(s.features, list) self.assertIsInstance( s.get_connection_pid('org.freedesktop.DBus'), int) self.assertIsInstance( s.get_connection_uid('org.freedesktop.DBus'), int) with self.assertRaises(DbusPropertyReadOnlyError): s.features = ['test'] self.assertTrue(s.get_name_owner('org.example.test')) with self.subTest('Test dbus to python name map'): self.assertTrue( any( "Features" in meta.dbus_member_to_python_attr for meta in ( meta for _, meta in s._dbus_iter_interfaces_meta() ) ) ) with self.subTest('Test properties_get_all_dict'): self.assertIn('features', s.properties_get_all_dict()) def test_docstring(self) -> None: from pydoc import getdoc s = FreedesktopDbus(self.bus) with self.subTest('Method doc'): self.assertTrue(getdoc(s.get_connection_pid)) with self.subTest('Property doc (through class dict)'): self.assertTrue(getdoc(s.__class__.__dict__['features'])) def test_interface_composition(self) -> None: class OneInterface( DbusInterfaceCommon, interface_name="org.example.one", ): @dbus_method(result_signature="x") def one(self) -> int: raise NotImplementedError class TwoInterface( DbusInterfaceCommon, interface_name="org.example.two", ): @dbus_method(result_signature="t") def two(self) -> int: raise NotImplementedError class CombinedInterface(OneInterface, TwoInterface): ... if __name__ == '__main__': main() python-sdbus-0.14.0/test/test_sdbus_block_bad_class.py000066400000000000000000000135401477456016000231250ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2023 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from gc import collect from unittest import TestCase from unittest import main as unittest_main from sdbus import DbusInterfaceCommon, dbus_method, dbus_property from .common_test_util import skip_if_no_name_validations class GoodDbusInterface( DbusInterfaceCommon, interface_name="org.example.test", ): @dbus_method() def test_method(self) -> None: raise NotImplementedError @dbus_property("s") def test_property(self) -> str: return "test" class TestBadDbusClass(TestCase): def test_method_name_override(self) -> None: with self.subTest("Method override"), self.assertRaises(ValueError): class BadMethodOverrideClass(GoodDbusInterface): def test_method(self) -> None: return with self.subTest("D-Bus method override"), self.assertRaises( ValueError ): class BadDbusMethodOverrideClass(GoodDbusInterface): @dbus_method() def test_method(self) -> None: return with self.subTest("Property override"), self.assertRaises(ValueError): class BadPropertyOverrideClass(GoodDbusInterface): def test_property(self) -> str: # type: ignore return "override" with self.subTest("D-Bus property override"), self.assertRaises( ValueError ): class BadDbusPropertyOverrideClass(GoodDbusInterface): @dbus_property("s") def test_property(self) -> str: return "override" with self.subTest("Good new method"): class GoodSubclass(GoodDbusInterface): def new_method(self) -> int: return 1 def test_interface_collision(self) -> None: with self.subTest("No collision"): class NonInterface(GoodDbusInterface): def do_work(self) -> None: ... with self.subTest("Collision"), self.assertRaises(ValueError): class NewExampleInterface( DbusInterfaceCommon, interface_name="org.example.test", ): ... def test_bad_class_names(self) -> None: skip_if_no_name_validations() with self.assertRaisesRegex(AssertionError, "^Invalid interface name"): class BadInterfaceName( DbusInterfaceCommon, interface_name="0.test", ): ... with self.assertRaisesRegex( AssertionError, "^Invalid method name", ): class BadMethodName( DbusInterfaceCommon, interface_name="org.example", ): @dbus_method( result_signature="s", method_name="🤫", ) def test(self) -> str: return "test" with self.assertRaisesRegex( AssertionError, "^Invalid property name", ): class BadPropertyName( DbusInterfaceCommon, interface_name="org.example", ): @dbus_property( property_signature="s", property_name="🤫", ) def test(self) -> str: return "test" def test_dbus_elements_without_interface_name(self) -> None: with self.assertRaisesRegex(TypeError, "without interface name"): class NoInterfaceName(DbusInterfaceCommon): @dbus_method() def example(self) -> None: ... def test_shared_parent_class(self) -> None: class One(GoodDbusInterface): ... class Two(GoodDbusInterface): ... class Shared(One, Two): ... def test_combined_collision(self) -> None: class One( DbusInterfaceCommon, interface_name="org.example.foo", ): @dbus_method() def example(self) -> None: ... class Two( DbusInterfaceCommon, interface_name="org.example.bar", ): @dbus_method() def example(self) -> None: ... with self.assertRaisesRegex(ValueError, "collision"): class Combined(One, Two): ... def test_class_cleanup(self) -> None: class One( DbusInterfaceCommon, interface_name="org.example.foo1", ): ... with self.assertRaises(ValueError): class Two( DbusInterfaceCommon, interface_name="org.example.foo1", ): ... del One collect() # Let weak refs be processed class After( DbusInterfaceCommon, interface_name="org.example.foo1", ): ... if __name__ == "__main__": unittest_main() python-sdbus-0.14.0/test/test_sdbus_utils.py000066400000000000000000000201341477456016000211750ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from unittest import TestCase from sdbus.unittest import IsolatedDbusTestCase from sdbus.utils.inspect import inspect_dbus_path from sdbus.utils.parse import parse_get_managed_objects from sdbus import ( DbusInterfaceCommon, DbusInterfaceCommonAsync, dbus_property, dbus_property_async, sd_bus_open_user, ) TEST_PATH = "/test" class FooAsync(DbusInterfaceCommonAsync, interface_name="org.foo"): @dbus_property_async("x") def foo(self) -> int: return 1 class BarAsync(DbusInterfaceCommonAsync, interface_name="org.bar"): @dbus_property_async("x") def bar(self) -> int: return 2 class FooBarAsync(FooAsync, BarAsync): ... class Foo(DbusInterfaceCommon, interface_name="org.foo"): @dbus_property("x") def foo(self) -> int: return 1 class Bar(DbusInterfaceCommon, interface_name="org.bar"): @dbus_property("x") def bar(self) -> int: return 2 class FooBar(Foo, Bar): ... MANAGED_OBJECTS_COMBINED = { "/test": { "org.foo": {"Foo": ("x", 1)}, "org.bar": {"Bar": ("x", 2)}, } } MANAGED_OBJECTS_SPLIT = { "/foo": { "org.foo": {"Foo": ("x", 1)}, }, "/bar": { "org.bar": {"Bar": ("x", 2)}, }, } MANAGED_OBJECTS_BOTH = {**MANAGED_OBJECTS_COMBINED, **MANAGED_OBJECTS_SPLIT} class TestSdbusUtilsParse(TestCase): def test_parse_get_managed_objects_async_combined(self) -> None: parsed_managed = parse_get_managed_objects( FooBarAsync, MANAGED_OBJECTS_COMBINED, ) self.assertEqual(1, len(parsed_managed)) class_type, properties_data = parsed_managed["/test"] self.assertEqual(FooBarAsync, class_type) self.assertEqual(properties_data["foo"], 1) self.assertEqual(properties_data["bar"], 2) def test_parse_get_managed_objects_block_combined(self) -> None: parsed_managed = parse_get_managed_objects( FooBar, MANAGED_OBJECTS_COMBINED, ) self.assertEqual(1, len(parsed_managed)) class_type, properties_data = parsed_managed["/test"] self.assertEqual(FooBar, class_type) self.assertEqual(properties_data["foo"], 1) self.assertEqual(properties_data["bar"], 2) def test_parse_get_managed_objects_async_split(self) -> None: parsed_managed = parse_get_managed_objects( [FooAsync, BarAsync], MANAGED_OBJECTS_SPLIT, ) self.assertEqual(2, len(parsed_managed)) class_type, properties_data = parsed_managed["/foo"] self.assertEqual(FooAsync, class_type) self.assertEqual(properties_data["foo"], 1) class_type, properties_data = parsed_managed["/bar"] self.assertEqual(BarAsync, class_type) self.assertEqual(properties_data["bar"], 2) def test_parse_get_managed_objects_block_split(self) -> None: parsed_managed = parse_get_managed_objects( [Foo, Bar], MANAGED_OBJECTS_SPLIT, ) self.assertEqual(2, len(parsed_managed)) class_type, properties_data = parsed_managed["/foo"] self.assertEqual(Foo, class_type) self.assertEqual(properties_data["foo"], 1) class_type, properties_data = parsed_managed["/bar"] self.assertEqual(Bar, class_type) self.assertEqual(properties_data["bar"], 2) def test_parse_get_managed_objects_unknown_interface_error(self) -> None: with self.assertRaisesRegex(KeyError, "org.foo"): parse_get_managed_objects( Bar, MANAGED_OBJECTS_SPLIT, ) def test_parse_get_managed_objects_unknown_interface_none_reuse( self, ) -> None: parsed_managed = parse_get_managed_objects( {BarAsync}, MANAGED_OBJECTS_SPLIT, on_unknown_interface="none", on_unknown_member="reuse", ) class_type, properties_data = parsed_managed["/foo"] self.assertIsNone(class_type) self.assertEqual(properties_data["Foo"], 1) class_type, properties_data = parsed_managed["/bar"] self.assertEqual(BarAsync, class_type) self.assertEqual(properties_data["bar"], 2) def test_parse_get_managed_objects_unknown_member_skip(self) -> None: parsed_managed = parse_get_managed_objects( [Foo], MANAGED_OBJECTS_SPLIT, on_unknown_interface="none", on_unknown_member="ignore", ) self.assertEqual(2, len(parsed_managed)) class_type, properties_data = parsed_managed["/foo"] self.assertEqual(Foo, class_type) self.assertEqual(properties_data["foo"], 1) class_type, properties_data = parsed_managed["/bar"] self.assertIsNone(class_type) self.assertEqual(0, len(properties_data)) def test_parse_get_managed_objects_interface_subset_single(self) -> None: parsed_managed = parse_get_managed_objects( [FooAsync], MANAGED_OBJECTS_COMBINED, on_unknown_interface="error", on_unknown_member="reuse", use_interface_subsets=True, ) class_type, properties_data = parsed_managed["/test"] self.assertIs(class_type, FooAsync) self.assertEqual(properties_data["foo"], 1) self.assertEqual(properties_data["Bar"], 2) def test_parse_get_managed_objects_interface_subset_multiple(self) -> None: parsed_managed = parse_get_managed_objects( [FooAsync, FooBarAsync], # FooBarAsync should be prioritized then both interfaces # are available on the path. MANAGED_OBJECTS_BOTH, on_unknown_interface="none", on_unknown_member="reuse", use_interface_subsets=True, ) class_type, _ = parsed_managed["/test"] self.assertIs(class_type, FooBarAsync) class_type, _ = parsed_managed["/foo"] self.assertIs(class_type, FooAsync) class_type, _ = parsed_managed["/bar"] self.assertIsNone(class_type) class TestSdbusUtilsInspect(IsolatedDbusTestCase): def test_inspect_dbus_path_block(self) -> None: proxy = DbusInterfaceCommon("example.org", TEST_PATH) self.assertEqual(inspect_dbus_path(proxy), TEST_PATH) new_bus = sd_bus_open_user() with self.assertRaisesRegex(LookupError, "is not attached to bus"): inspect_dbus_path(proxy, new_bus) def test_inspect_dbus_path_async_proxy(self) -> None: proxy = DbusInterfaceCommonAsync.new_proxy("example.org", TEST_PATH) self.assertEqual(inspect_dbus_path(proxy), TEST_PATH) new_bus = sd_bus_open_user() with self.assertRaisesRegex(LookupError, "is not attached to bus"): inspect_dbus_path(proxy, new_bus) def test_inspect_dbus_path_async_local(self) -> None: local_obj = FooBarAsync() with self.assertRaisesRegex( LookupError, "is not exported to any D-Bus", ): inspect_dbus_path(local_obj) local_obj.export_to_dbus(TEST_PATH) self.assertEqual(inspect_dbus_path(local_obj), TEST_PATH) new_bus = sd_bus_open_user() with self.assertRaisesRegex(LookupError, "is not attached to bus"): inspect_dbus_path(local_obj, new_bus) python-sdbus-0.14.0/test/test_typing.py000066400000000000000000000104451477456016000201530ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2024 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from sdbus import ( DbusInterfaceCommon, DbusInterfaceCommonAsync, dbus_method, dbus_method_async, dbus_property, dbus_property_async, dbus_signal_async, ) class TestTypingBlocking( DbusInterfaceCommon, interface_name="example.com", ): @dbus_method(result_signature="as") def get_str_list_method(self) -> list[str]: raise NotImplementedError @dbus_property("as") def str_list_property(self) -> list[str]: raise NotImplementedError # These functions are not meant to be executed # but exist to be type checked. def check_blocking_interface_method_typing( test_interface: TestTypingBlocking, ) -> None: should_be_list = test_interface.get_str_list_method() should_be_list.append("test") for x in should_be_list: x.capitalize() def check_blocking_interface_property_typing( test_interface: TestTypingBlocking, ) -> None: should_be_list = test_interface.str_list_property should_be_list.append("test") for x in should_be_list: x.capitalize() test_interface.str_list_property = ["test", "foobar"] class TestTypingAsync( DbusInterfaceCommonAsync, interface_name="example.com", ): @dbus_method_async(result_signature="as") async def get_str_list_method(self) -> list[str]: raise NotImplementedError @dbus_property_async("as") def str_list_property(self) -> list[str]: raise NotImplementedError @dbus_signal_async("as") def str_list_signal(self) -> list[str]: raise NotImplementedError async def check_async_interface_method_typing( test_interface: TestTypingAsync, ) -> None: should_be_list = await test_interface.get_str_list_method() should_be_list.append("test") for x in should_be_list: x.capitalize() async def check_async_interface_property_typing( test_interface: TestTypingAsync, ) -> None: should_be_list = await test_interface.str_list_property should_be_list.append("test") for x in should_be_list: x.capitalize() should_be_list2 = await test_interface.str_list_property.get_async() should_be_list2.append("test") for x in should_be_list2: x.capitalize() await test_interface.str_list_property.set_async(["test", "foobar"]) async def check_async_interface_signal_typing( test_interface: TestTypingAsync, ) -> None: async for ls in test_interface.str_list_signal: ls.append("test") for x in ls: x.capitalize() async for ls2 in test_interface.str_list_signal.catch(): ls2.append("test") for x2 in ls2: x2.capitalize() async for p1, ls3 in test_interface.str_list_signal.catch_anywhere(): p1.capitalize() ls3.append("test") for x3 in ls3: x3.capitalize() async for p2, ls4 in ( TestTypingAsync.str_list_signal .catch_anywhere("org.example") ): p2.capitalize() ls4.append("test") for x4 in ls4: x4.capitalize() test_interface.str_list_signal.emit(["test", "foobar"]) async def check_async_element_class_access_typing() -> None: test_list: list[str] = [] # TODO: Fix dbus async method typing # test_list.append( # TestTypingAsync.get_str_list_method.method_name # ) test_list.append( TestTypingAsync.str_list_property.property_name ) test_list.append( TestTypingAsync.str_list_signal.signal_name ) python-sdbus-0.14.0/tools/000077500000000000000000000000001477456016000154055ustar00rootroot00000000000000python-sdbus-0.14.0/tools/run_py_linters.py000077500000000000000000000066641477456016000210520ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2020, 2021 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from argparse import ArgumentParser from os import environ from pathlib import Path from subprocess import SubprocessError, run source_root = Path(environ['MESON_SOURCE_ROOT']) build_dir = Path(environ['MESON_BUILD_ROOT']) tools_dir = source_root / 'tools' src_dir = source_root / 'src' test_dir = source_root / 'test' wheel_build_dir = source_root / 'wheel-build' examples_dir = source_root / 'examples' all_python_modules = [ tools_dir, test_dir, wheel_build_dir, src_dir / 'sdbus', src_dir / 'sdbus_async/dbus_daemon', src_dir / 'sdbus_block/dbus_daemon', source_root / 'setup.py', ] mypy_cache_dir = build_dir / '.mypy_cache' def run_mypy() -> None: print('Running mypy on all modules') run( args=( 'mypy', '--strict', '--pretty', '--cache-dir', mypy_cache_dir, '--python-version', '3.9', '--namespace-packages', '--explicit-package-bases', *all_python_modules, ), check=True, env={'MYPYPATH': str(src_dir.absolute()), **environ}, ) def run_flake8() -> None: run( args=( 'flake8', *all_python_modules, ), check=True, ) def linter_main() -> None: is_success = True try: run_flake8() except SubprocessError: is_success = False try: run_mypy() except SubprocessError: is_success = False if not is_success: raise SystemExit(1) def get_all_python_files() -> list[Path]: python_files: list[Path] = [source_root / 'setup.py'] for python_module in all_python_modules: if python_module.is_dir(): for a_file in python_module.iterdir(): if a_file.suffix == '.py': python_files.append(a_file) else: python_files.append(python_module) return python_files def formater_main() -> None: run( args=('autopep8', '--recursive', '--in-place', *all_python_modules), check=True, ) run( args=( 'isort', '-m', 'VERTICAL_HANGING_INDENT', '--trailing-comma', *all_python_modules, ), check=True, ) def main() -> None: parser = ArgumentParser() parser.add_argument( 'mode', choices=('lint', 'format'), ) args = parser.parse_args() mode = args.mode if mode == 'lint': linter_main() elif mode == 'format': formater_main() else: raise ValueError('Unknown mode', mode) if __name__ == '__main__': main() python-sdbus-0.14.0/tox.ini000066400000000000000000000001161477456016000155560ustar00rootroot00000000000000[tox] envlist = py38,py39 [testenv] commands = python -m unittest --verbose python-sdbus-0.14.0/wheel-build/000077500000000000000000000000001477456016000164465ustar00rootroot00000000000000python-sdbus-0.14.0/wheel-build/audit_wheel_wrapper.py000066400000000000000000000013341477456016000230530ustar00rootroot00000000000000# SPDX-License-Identifier: MPL-2.0 # SPDX-FileCopyrightText: 2024 igo95862 from __future__ import annotations from argparse import ArgumentParser from unittest.mock import patch from auditwheel.main import main as auditwheel_main # type: ignore def main(arch: str, wrapped_args: list[str]) -> None: with patch("sys.argv", [""] + wrapped_args), patch( "platform.machine", return_value=arch ): auditwheel_main() if __name__ == "__main__": arg_parse = ArgumentParser() arg_parse.add_argument( "--arch", choices=("x86_64", "i686", "aarch64", "armv7l"), default="x86_64", ) arg_parse.add_argument("wrapped_args", nargs="*") main(**vars(arg_parse.parse_args())) python-sdbus-0.14.0/wheel-build/run_podman_full_build.py000066400000000000000000000212001477456016000233560ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2025 igo95862 # This file is part of python-sdbus # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations from argparse import ArgumentParser from collections.abc import Callable, Iterator from functools import partial from pathlib import Path from subprocess import PIPE from subprocess import run as _run SDBUS_REFSPEC = "HEAD" SDBUS_SRC_DIR = Path("/root/sdbus") WHEEL_BUILD_DIR = Path(__file__).parent PROJECT_ROOT = WHEEL_BUILD_DIR.parent BUILD_DIR = PROJECT_ROOT / "build/wheel-build/" LAST_STAGE_FILE = BUILD_DIR / "last_stage" CONTAINER_IMAGE = "docker.io/debian:11-slim" CONTAINER_NAME = "python-sdbus-build" CONTAINER_ARCH = "x86_64" DEBIAN_PACKAGES = ( "python3", "python3-dev", "gcc", "gperf", "meson", "python3-wheel", "python-setuptools", "python3-jinja2", "libcap-dev", "libmount-dev", "git", "ca-certificates", "pkg-config", ) DEBIAN_NAME = "bullseye" BASIC_CFLAGS: list[str] = [ '-O2', '-fno-plt', '-D_FORTIFY_SOURCE=2', '-fstack-clash-protection', ] SYSTEMD_REPO = "https://github.com/systemd/systemd-stable.git" # systemd 255 is last one before glibc 2.31 requirement SYSTEMD_TAG = "v255.18" SYSTEMD_SRC_DIR = Path("/root/systemd") SYSTEMD_BUILD_DIR = SYSTEMD_SRC_DIR / "build" SYSTEMD_COMPAT_PATCH_NAME = "systemd_no_gettid_no_getdents64.patch" SYSTEMD_COMPAT_PATCH_FILE = WHEEL_BUILD_DIR / SYSTEMD_COMPAT_PATCH_NAME SYSTEMD_OPTIONS: list[str] = [ "static-libsystemd=pic", "tests=false", "coredump=false", "dbus=false", "efi=false", "elfutils=false", "hostnamed=false", "homed=false", "importd=false", "initrd=false", "kernel-install=false", "logind=false", "machined=false", "man=false", "networkd=false", "portabled=false", "repart=false", "sysext=false", "sysusers=false", "timedated=false", "timesyncd=false", "tmpfiles=false", "oomd=false", "hibernate=false", "nss-systemd=false", "nss-resolve=false", ] run = partial(_run, check=True, cwd=PROJECT_ROOT) def podman_exec( *args: str, env: dict[str, str] | None = None, cwd: Path | None = None, input: bytes | None = None, ) -> None: env_list = [ f"--env={env_k}={env_v}" for env_k, env_v in env.items() ] if env else [] workdir_options = [f"--workdir={cwd}"] if cwd else [] run( args=( "podman", "exec", *env_list, *workdir_options, "--tty" if input is None else "--interactive", CONTAINER_NAME, *args, ), input=input, ) def podman_cp(src: Path, dest: Path, to_contatiner: bool = True) -> None: if to_contatiner: src_str = str(src.absolute()) dest_str = f"{CONTAINER_NAME}:{dest}" else: src_str = f"{CONTAINER_NAME}:{src}" dest_str = str(dest.absolute()) run( args=("podman", "cp", src_str, dest_str) ) def podman_start() -> None: run( args=( "podman", "run", "--name", CONTAINER_NAME, "--arch", CONTAINER_ARCH, "--detach", "--rm", "--init", CONTAINER_IMAGE, "sleep", "3d", ) ) def install_packages() -> None: target_release = ("--target-release", f"{DEBIAN_NAME}-backports") deb_env = {"DEBIAN_FRONTEND": "noninteractive"} podman_exec( "bash", "-c", f"echo 'deb http://deb.debian.org/debian {DEBIAN_NAME}-backports main'" " > /etc/apt/sources.list.d/backports.list" ) podman_exec("apt-get", "update", env=deb_env) podman_exec( "apt-get", "upgrade", *target_release, "--yes", env=deb_env, ) podman_exec( "apt-get", "install", *target_release, "--yes", "--no-install-recommends", *DEBIAN_PACKAGES, env=deb_env, ) def clone_systemd() -> None: podman_exec( "git", "clone", "--depth", "1", "--branch", SYSTEMD_TAG, "--", SYSTEMD_REPO, str(SYSTEMD_SRC_DIR), ) def apply_systemd_patch() -> None: podman_cp(SYSTEMD_COMPAT_PATCH_FILE, SYSTEMD_SRC_DIR) podman_exec( "git", "apply", SYSTEMD_COMPAT_PATCH_NAME, cwd=SYSTEMD_SRC_DIR, ) def build_systemd() -> None: systemd_options_get = (f"-D{o}" for o in SYSTEMD_OPTIONS) cflags = {"CFLAGS": " ".join(BASIC_CFLAGS)} podman_exec( "meson", "setup", "--auto-features=disabled", "--buildtype=release", *systemd_options_get, str(SYSTEMD_BUILD_DIR), cwd=SYSTEMD_SRC_DIR, env=cflags, ) podman_exec( "meson", "compile", "systemd:static_library", "libsystemd.pc", cwd=SYSTEMD_BUILD_DIR, ) def install_systemd_files() -> None: podman_exec( "bash", "-c", "cp libsystemd.a" " /usr/lib/$(cat /usr/lib/pkg-config.multiarch)/", cwd=SYSTEMD_BUILD_DIR, ) podman_exec( "cp", "src/libsystemd/libsystemd.pc", "/usr/share/pkgconfig/", cwd=SYSTEMD_BUILD_DIR, ) podman_exec( "cp", "src/libsystemd/libsystemd.pc", "/usr/share/pkgconfig/", cwd=SYSTEMD_BUILD_DIR, ) podman_exec( "mkdir", "--parents", "/usr/include/systemd/" ) required_headers = ( "_sd-common.h", "sd-id128.h", "sd-daemon.h", "sd-bus.h", "sd-bus-vtable.h", "sd-bus-protocol.h", "sd-device.h", "sd-event.h", ) podman_exec( "cp", *(f"src/systemd/{h}" for h in required_headers), "/usr/include/systemd/", cwd=SYSTEMD_SRC_DIR, ) def copy_sdbus_sources() -> None: podman_exec("mkdir", "--parents", str(SDBUS_SRC_DIR)) sdbus_tar = run( args=("git", "archive", SDBUS_REFSPEC), stdout=PIPE, ).stdout assert isinstance(sdbus_tar, bytes) print("python-sdbus source archive size:", len(sdbus_tar)) podman_exec( "tar", "--extract", "--verbose", cwd=SDBUS_SRC_DIR, input=sdbus_tar, ) def compile_sdbus() -> None: podman_exec( "python3", "setup.py", "build", "bdist_wheel", "--py-limited-api", "cp39", cwd=SDBUS_SRC_DIR, env={ "PYTHON_SDBUS_USE_STATIC_LINK": "1", "PYTHON_SDBUS_USE_LIMITED_API": "1", "CFLAGS": " ".join(BASIC_CFLAGS), }, ) def copy_dist() -> None: podman_cp( SDBUS_SRC_DIR / "dist", BUILD_DIR / f"{CONTAINER_ARCH}-dist", to_contatiner=False, ) STAGES: dict[str, Callable[[], None]] = { "podman_start": podman_start, "install_packages": install_packages, "clone_systemd": clone_systemd, "apply_systemd_patch": apply_systemd_patch, "build_systemd": build_systemd, "install_systemd_files": install_systemd_files, "copy_sdbus_sources": copy_sdbus_sources, "compile_sdbus": compile_sdbus, "copy_dist": copy_dist, } def iter_stages() -> Iterator[tuple[str, Callable[[], None]]]: stages_iter = iter(STAGES.items()) if LAST_STAGE_FILE.exists(): last_stage = LAST_STAGE_FILE.read_text().strip() for stage_name, _ in stages_iter: if stage_name == last_stage: print("Last completed stage:", stage_name) break else: print("Skipping stage:", stage_name) yield from stages_iter def main() -> None: args_parser = ArgumentParser() args_parser.add_argument("--arch") args = args_parser.parse_args() if arch := args.arch: global CONTAINER_ARCH CONTAINER_ARCH = arch BUILD_DIR.mkdir(parents=True, exist_ok=True) for stage_name, stage_func in iter_stages(): stage_func() LAST_STAGE_FILE.write_text(stage_name) print("Completed:", stage_name) if __name__ == "__main__": main() python-sdbus-0.14.0/wheel-build/systemd_no_gettid_no_getdents64.patch000066400000000000000000000023471477456016000257640ustar00rootroot00000000000000diff --git a/meson.build b/meson.build index 8c16c1c5c0..f34e4a0c3a 100644 --- a/meson.build +++ b/meson.build @@ -573,8 +573,6 @@ endforeach foreach ident : [ ['memfd_create', '''#include '''], - ['gettid', '''#include - #include '''], ['fchmodat2', '''#include #include '''], # no known header declares fchmodat2 ['pivot_root', '''#include @@ -631,13 +629,15 @@ foreach ident : [ ['fsopen', '''#include '''], ['fsconfig', '''#include '''], ['fsmount', '''#include '''], - ['getdents64', '''#include '''], ] have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') conf.set10('HAVE_' + ident[0].to_upper(), have) endforeach +conf.set10('HAVE_GETTID', false) +conf.set10('HAVE_GETDENTS64', false) + if cc.has_function('getrandom', prefix : '''#include ''', args : '-D_GNU_SOURCE') conf.set10('USE_SYS_RANDOM_H', true) conf.set10('HAVE_GETRANDOM', true)