Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-jeepney for openSUSE:Factory checked in at 2025-07-15 16:42:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jeepney (Old) and /work/SRC/openSUSE:Factory/.python-jeepney.new.7373 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jeepney" Tue Jul 15 16:42:59 2025 rev:12 rq:1292494 version:0.9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jeepney/python-jeepney.changes 2025-05-31 19:14:48.085636814 +0200 +++ /work/SRC/openSUSE:Factory/.python-jeepney.new.7373/python-jeepney.changes 2025-07-15 16:43:36.063302650 +0200 @@ -1,0 +2,15 @@ +Sun Jul 13 12:14:17 UTC 2025 - Dirk Müller <dmuel...@suse.com> + +- update to 0.9.0: + * Fixed subscribing to messages on the message bus with a + path_namespace parameter (:mr:`38`) + * Fixed authentication on (some?) BSDs, using SCM_CREDS + (:mr:`33`), for all integrations except for asyncio + * :class:`~.DBusAddress` and message generators will now raise + :exc:`ValueError` if given invalid D-Bus names + * Bindings can now be :doc:`generated <bindgen>` from D-Bus XML + in a file with the new :option:`--file` option (:mr:`34`). + * The async_timeout package is no longer required for running + the tests on Python 3.11 or above (:mr:`39`). + +------------------------------------------------------------------- Old: ---- jeepney-0.8.0.tar.gz New: ---- jeepney-0.9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jeepney.spec ++++++ --- /var/tmp/diff_new_pack.G4sYnN/_old 2025-07-15 16:43:36.855332105 +0200 +++ /var/tmp/diff_new_pack.G4sYnN/_new 2025-07-15 16:43:36.855332105 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-jeepney -Version: 0.8.0 +Version: 0.9.0 Release: 0 Summary: Low-level, pure Python DBus protocol wrapper License: MIT ++++++ jeepney-0.8.0.tar.gz -> jeepney-0.9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/.gitignore new/jeepney-0.9.0/.gitignore --- old/jeepney-0.8.0/.gitignore 2018-09-11 21:07:32.033692000 +0200 +++ new/jeepney-0.9.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -__pycache__/ -/dist/ -/docs/_build/ -.pytest_cache/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/.gitlab-ci.yml new/jeepney-0.9.0/.gitlab-ci.yml --- old/jeepney-0.8.0/.gitlab-ci.yml 2022-04-03 19:31:55.796912400 +0200 +++ new/jeepney-0.9.0/.gitlab-ci.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -before_script: - - pip install '.[test]' - -test_job_py310: - image: python:3.10 - script: - - pytest - stage: test - -test_job_py39: - image: python:3.9 - script: - - pytest - stage: test - -test_job_py38: - image: python:3.8 - script: - - pytest - stage: test - -test_job_py37: - image: python:3.7 - script: - - pytest - stage: test - -test_job_integration: - image: fedora:32 - before_script: - - dnf install -y dbus-daemon python3-pip - - pip install '.[test,trio]' - script: - - dbus-run-session -- pytest - stage: test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/PKG-INFO new/jeepney-0.9.0/PKG-INFO --- old/jeepney-0.8.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/jeepney-0.9.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,24 +1,23 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: jeepney -Version: 0.8.0 +Version: 0.9.0 Summary: Low-level, pure Python DBus protocol wrapper. -Home-page: https://gitlab.com/takluyver/jeepney -Author: Thomas Kluyver -Author-email: tho...@kluyver.me.uk +Author-email: Thomas Kluyver <tho...@kluyver.me.uk> Requires-Python: >=3.7 Description-Content-Type: text/x-rst -Classifier: License :: OSI Approved :: MIT License +License-Expression: MIT Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Desktop Environment +License-File: LICENSE Requires-Dist: pytest ; extra == "test" Requires-Dist: pytest-trio ; extra == "test" Requires-Dist: pytest-asyncio >=0.17 ; extra == "test" Requires-Dist: testpath ; extra == "test" Requires-Dist: trio ; extra == "test" -Requires-Dist: async-timeout ; extra == "test" +Requires-Dist: async-timeout ; extra == "test" and ( python_version < '3.11') Requires-Dist: trio ; extra == "trio" -Requires-Dist: async_generator ; extra == "trio" and ( python_version == '3.6') Project-URL: Documentation, https://jeepney.readthedocs.io/en/latest/ +Project-URL: Source, https://gitlab.com/takluyver/jeepney Provides-Extra: test Provides-Extra: trio diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/docs/bindgen.rst new/jeepney-0.9.0/docs/bindgen.rst --- old/jeepney-0.8.0/docs/bindgen.rst 2021-07-10 17:19:49.182029500 +0200 +++ new/jeepney-0.9.0/docs/bindgen.rst 2025-02-27 19:48:26.862029000 +0100 @@ -18,3 +18,33 @@ You are welcome to edit the generated code, e.g. to add docstrings or give parameters meaningful names. Names like ``arg_1`` are created when introspection doesn't provide a name. + + +Bindgen command options +----------------------- + +.. program:: python -m jeepney.bindgen + +.. option:: -n <bus name>, --name <bus name> + + Bus name to introspect, required unless using :option:`--file`. + +.. option:: -p <object path>, --path <object path> + + Object path to introspect, required unless using :option:`--file`. + Bindings will be generated for all interfaces this object exposes, except + for common interfaces like 'Introspectable'. + +.. option:: --bus <bus> + + Bus to connect to, SESSION (default) or SYSTEM. + +.. option:: -f <path>, --file <path> + + An XML file to use as input instead of connecting to D-Bus and using + introspection. The options above are ignored if this is used. + +.. option:: -o <path>, --output <path> + + Write the output (Python code) to the specified file. + By default, a filename is chosen based on the input. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/docs/conf.py new/jeepney-0.9.0/docs/conf.py --- old/jeepney-0.8.0/docs/conf.py 2021-07-10 17:19:49.185029500 +0200 +++ new/jeepney-0.9.0/docs/conf.py 2025-02-27 19:48:26.862029000 +0100 @@ -34,8 +34,14 @@ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', + 'sphinx.ext.extlinks', ] +extlinks = { + 'issue': ('https://gitlab.com/takluyver/jeepney/-/issues/%s', "issue #%s"), + 'mr': ('https://gitlab.com/takluyver/jeepney/-/merge_requests/%s', "MR !%s"), +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -129,10 +135,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -import sphinx_rtd_theme - html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/docs/release-notes.rst new/jeepney-0.9.0/docs/release-notes.rst --- old/jeepney-0.8.0/docs/release-notes.rst 2022-04-03 19:51:40.362281800 +0200 +++ new/jeepney-0.9.0/docs/release-notes.rst 2025-02-27 19:48:26.862029000 +0100 @@ -1,6 +1,32 @@ Release notes ============= +0.9 +--- + +2025-02-27 + +* Fixed subscribing to messages on the message bus with a ``path_namespace`` + parameter (:mr:`38`) +* Fixed authentication on (some?) BSDs, using SCM_CREDS (:mr:`33`), for all + integrations except for asyncio (which does not expose ``sendmsg``). +* :class:`~.DBusAddress` and message generators will now raise :exc:`ValueError` + if given invalid D-Bus names - bus names, object paths, or interface names + (:mr:`36`). Previously these could easily be sent in messages, resulting in + the bus closing the connection. +* Bindings can now be :doc:`generated <bindgen>` from D-Bus XML in a file + with the new :option:`--file` option (:mr:`34`). +* The ``async_timeout`` package is no longer required for running the tests on + Python 3.11 or above (:mr:`39`). + +Breaking changes +~~~~~~~~~~~~~~~~ + +* Removed the deprecated ``connection.router`` API in the blocking IO + integration (:mr:`40`). +* Removed the deprecated ``unwrap`` parameter from ``send_and_get_reply`` in + the blocking integration (:mr:`37`). + 0.8 --- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/__init__.py new/jeepney-0.9.0/jeepney/__init__.py --- old/jeepney-0.8.0/jeepney/__init__.py 2022-04-03 19:52:40.934471100 +0200 +++ new/jeepney-0.9.0/jeepney/__init__.py 2025-02-27 19:48:26.865029000 +0100 @@ -10,4 +10,4 @@ from .fds import FileDescriptor, NoFDError from .wrappers import * -__version__ = '0.8.0' +__version__ = '0.9.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/auth.py new/jeepney-0.9.0/jeepney/auth.py --- old/jeepney-0.8.0/jeepney/auth.py 2021-07-10 17:19:49.190029600 +0200 +++ new/jeepney-0.9.0/jeepney/auth.py 2025-02-27 19:48:26.865029000 +0100 @@ -54,12 +54,20 @@ """Process data for the SASL authentication conversation If enable_fds is True, this includes negotiating support for passing - file descriptors. + file descriptors. If inc_null_byte is True, sends the '\0' byte + at the beginning of the negotiations, which was the past behavior, + but which prevents sending the SCM_CREDS ancillary data over the socket, + breaking authentication on *BSD; the caller should rather send that + null byte and ancillary data and pass inc_null_byte=False to prevent + it being done here. """ - def __init__(self, enable_fds=False): + def __init__(self, enable_fds=False, inc_null_byte=True): self.enable_fds = enable_fds self.buffer = bytearray() - self._to_send = b'\0' + make_auth_external() + if inc_null_byte: + self._to_send = b'\0' + make_auth_external() + else: + self._to_send = make_auth_external() self.state = ClientState.WaitingForOk self.error = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/bindgen.py new/jeepney-0.9.0/jeepney/bindgen.py --- old/jeepney-0.8.0/jeepney/bindgen.py 2021-07-10 17:19:49.190029600 +0200 +++ new/jeepney-0.9.0/jeepney/bindgen.py 2025-02-27 19:48:26.865029000 +0100 @@ -1,7 +1,9 @@ """Generate a wrapper class from DBus introspection data""" import argparse -from textwrap import indent +import os.path +import sys import xml.etree.ElementTree as ET +from textwrap import indent from jeepney.wrappers import Introspectable from jeepney.io.blocking import open_dbus_connection, Proxy @@ -42,8 +44,8 @@ class {cls_name}(MessageGenerator): interface = {interface!r} - def __init__(self, object_path={path!r}, - bus_name={bus_name!r}): + def __init__(self, object_path{path_default}, + bus_name{name_default}): super().__init__(object_path=object_path, bus_name=bus_name) """ @@ -56,8 +58,12 @@ def make_code(self): cls_name = self.name.split('.')[-1] - chunks = [INTERFACE_CLASS_TEMPLATE.format(cls_name=cls_name, - interface=self.name, path=self.path, bus_name=self.bus_name)] + chunks = [INTERFACE_CLASS_TEMPLATE.format( + cls_name=cls_name, + interface=self.name, + path_default='' if self.path is None else f'={self.path!r}', + name_default='' if self.bus_name is None else f'={self.bus_name!r}' + )] for method in self.methods: chunks.append(indent(method.make_code(), ' ' * 4)) return '\n'.join(chunks) @@ -100,7 +106,12 @@ return i -def generate(path, name, output_file, bus='SESSION'): +def generate_from_introspection(path, name, output_file, bus='SESSION'): + # Many D-Bus services have a main object at a predictable name, e.g. + # org.freedesktop.Notifications -> /org/freedesktop/Notifications + if not path: + path = '/' + name.replace('.', '/') + conn = open_dbus_connection(bus) introspectable = Proxy(Introspectable(path, name), conn) xml, = introspectable.Introspect() @@ -109,17 +120,50 @@ n_interfaces = code_from_xml(xml, path, name, output_file) print("Written {} interface wrappers to {}".format(n_interfaces, output_file)) +def generate_from_file(input_file, path, name, output_file): + with open(input_file, encoding='utf-8') as f: + xml = f.read() + + n_interfaces = code_from_xml(xml, path, name, output_file) + print("Written {} interface wrappers to {}".format(n_interfaces, output_file)) + def main(): - ap = argparse.ArgumentParser() - ap.add_argument('-n', '--name', required=True) - ap.add_argument('-p', '--path', required=True) - ap.add_argument('--bus', default='SESSION') - ap.add_argument('-o', '--output') + ap = argparse.ArgumentParser( + description="Generate a simple wrapper module to call D-Bus methods.", + epilog="If you don't use --file, this will connect to D-Bus and introspect the " + "given name and path. --name and --path can also be used with --file, " + "to give defaults for the generated class." + ) + ap.add_argument('-n', '--name', + help='Bus name to introspect, required unless using file') + ap.add_argument('-p', '--path', + help='Object path to introspect. If not specified, a path matching ' + 'the name will be used, e.g. /org/freedesktop/Notifications for org.freedesktop.Notifications') + ap.add_argument('--bus', default='SESSION', + help='Bus to connect to for introspection (SESSION/SYSTEM), default SESSION') + ap.add_argument('-f', '--file', + help='XML file to use instead of D-Bus introspection') + ap.add_argument('-o', '--output', + help='Output filename') args = ap.parse_args() - output = args.output or (args.path[1:].replace('/', '_') + '.py') + if not (args.file or args.name): + sys.exit("Either --name or --file is required") - generate(args.path, args.name, output, args.bus) + # If no --output, guess a (hopefully) reasonable name. + if args.output: + output = args.output + elif args.file: + output = os.path.splitext(os.path.basename(args.file))[0] + '.py' + elif args.path and len(args.path) > 1: + output = args.path[1:].replace('/', '_') + '.py' + else: # e.g. path is '/' + output = args.name.replace('.', '_') + '.py' + + if args.file: + generate_from_file(args.file, args.path, args.name, output) + else: + generate_from_introspection(args.path, args.name, output, args.bus) if __name__ == '__main__': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/bus_messages.py new/jeepney-0.9.0/jeepney/bus_messages.py --- old/jeepney-0.8.0/jeepney/bus_messages.py 2021-07-10 17:19:49.190029600 +0200 +++ new/jeepney-0.9.0/jeepney/bus_messages.py 2025-02-27 19:48:26.865029000 +0100 @@ -176,6 +176,9 @@ if self.message_type: pairs.append(('type', self.message_type.name)) + if self.path_namespace: + pairs.append(('path_namespace', self.path_namespace)) + if self.eavesdrop: pairs.append(('eavesdrop', 'true')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/io/blocking.py new/jeepney-0.9.0/jeepney/io/blocking.py --- old/jeepney-0.8.0/jeepney/io/blocking.py 2022-04-03 19:31:55.796912400 +0200 +++ new/jeepney-0.9.0/jeepney/io/blocking.py 2025-02-27 19:48:26.866029000 +0100 @@ -10,14 +10,12 @@ import socket import time from typing import Optional -from warnings import warn from jeepney import Parser, Message, MessageType, HeaderFields from jeepney.auth import Authenticator, BEGIN from jeepney.bus import get_bus from jeepney.fds import FileDescriptor, fds_buf_size from jeepney.wrappers import ProxyBase, unwrap_msg -from jeepney.routing import Router from jeepney.bus_messages import message_bus from .common import MessageFilters, FilterHandle, check_replyable @@ -131,7 +129,6 @@ super().__init__(sock, enable_fds) # Message routing machinery - self._router = Router(_Future) # Old interface, for backwards compat self._filters = MessageFilters() # Say Hello, get our unique name @@ -139,12 +136,6 @@ hello_reply = self.bus_proxy.Hello() self.unique_name = hello_reply[0] - @property - def router(self): - warn("conn.router is deprecated, see the docs for APIs to use instead.", - stacklevel=2) - return self._router - def send(self, message: Message, serial=None): """Serialise and send a :class:`~.Message` object""" data, fds = self._serialise(message, serial) @@ -170,11 +161,10 @@ See :meth:`filter`. Returns nothing. """ msg = self.receive(timeout=timeout) - self._router.incoming(msg) for filter in self._filters.matches(msg): filter.queue.append(msg) - def send_and_get_reply(self, message, *, timeout=None, unwrap=None): + def send_and_get_reply(self, message, *, timeout=None): """Send a message, wait for the reply and return it Filters are applied to other messages received before the reply - @@ -183,24 +173,15 @@ check_replyable(message) deadline = timeout_to_deadline(timeout) - if unwrap is None: - unwrap = False - else: - warn("Passing unwrap= to .send_and_get_reply() is deprecated and " - "will break in a future version of Jeepney.", stacklevel=2) - serial = next(self.outgoing_serial) self.send_message(message, serial=serial) while True: msg_in = self.receive(timeout=deadline_to_timeout(deadline)) reply_to = msg_in.header.fields.get(HeaderFields.reply_serial, -1) if reply_to == serial: - if unwrap: - return unwrap_msg(msg_in) return msg_in # Not the reply - self._router.incoming(msg_in) for filter in self._filters.matches(msg_in): filter.queue.append(msg_in) @@ -308,7 +289,13 @@ try: with_sock_deadline(sock.connect, addr) - authr = Authenticator(enable_fds=enable_fds) + authr = Authenticator(enable_fds=enable_fds, inc_null_byte=False) + if hasattr(socket, 'SCM_CREDS'): + # BSD: send credentials message to authenticate (kernel fills in data) + sock.sendmsg([b'\0'], [(socket.SOL_SOCKET, socket.SCM_CREDS, bytes(512))]) + else: + # Linux: no ancillary data needed, bus checks with SO_PEERCRED + sock.send(b'\0') for req_data in authr: with_sock_deadline(sock.sendall, req_data) authr.feed(unwrap_read(with_sock_deadline(sock.recv, 1024))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/io/tests/test_asyncio.py new/jeepney-0.9.0/jeepney/io/tests/test_asyncio.py --- old/jeepney-0.8.0/jeepney/io/tests/test_asyncio.py 2022-04-03 19:31:55.797912400 +0200 +++ new/jeepney-0.9.0/jeepney/io/tests/test_asyncio.py 2025-02-27 19:48:26.866029000 +0100 @@ -1,6 +1,10 @@ import asyncio +import sys -import async_timeout +if sys.version_info >= (3, 11): + from asyncio import timeout +else: + from async_timeout import timeout import pytest import pytest_asyncio @@ -85,7 +89,7 @@ conn = await open_dbus_connection(bus='SESSION') try: with pytest.raises(asyncio.TimeoutError): - async with async_timeout.timeout(0): + async with timeout(0): await conn.receive() finally: await conn.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/io/tests/test_blocking.py new/jeepney-0.9.0/jeepney/io/tests/test_blocking.py --- old/jeepney-0.8.0/jeepney/io/tests/test_blocking.py 2022-04-03 19:31:55.797912400 +0200 +++ new/jeepney-0.9.0/jeepney/io/tests/test_blocking.py 2025-02-27 19:48:26.866029000 +0100 @@ -30,10 +30,6 @@ assert reply.header.message_type == MessageType.method_return assert reply.body == () - ping_call = new_method_call(bus_peer, 'Ping') - reply_body = session_conn.send_and_get_reply(ping_call, timeout=5, unwrap=True) - assert reply_body == () - def test_proxy(session_conn): proxy = Proxy(message_bus, session_conn, timeout=5) name = "io.gitlab.takluyver.jeepney.examples.Server" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/io/trio.py new/jeepney-0.9.0/jeepney/io/trio.py --- old/jeepney-0.8.0/jeepney/io/trio.py 2021-07-10 17:19:49.193029600 +0200 +++ new/jeepney-0.9.0/jeepney/io/trio.py 2025-02-27 19:48:26.866029000 +0100 @@ -1,15 +1,11 @@ import array -from contextlib import contextmanager import errno -from itertools import count import logging +import socket +from contextlib import asynccontextmanager, contextmanager +from itertools import count from typing import Optional -try: - from contextlib import asynccontextmanager # Python 3.7 -except ImportError: - from async_generator import asynccontextmanager # Backport for Python 3.6 - from outcome import Value, Error import trio from trio.abc import Channel @@ -195,7 +191,15 @@ sock : trio.SocketStream = await trio.open_unix_socket(bus_addr) # Authentication - authr = Authenticator(enable_fds=enable_fds) + authr = Authenticator(enable_fds=enable_fds, inc_null_byte=False) + if hasattr(socket, 'SCM_CREDS'): + # BSD: send credentials message to authenticate (kernel fills in data) + await sock.socket.sendmsg( + [b'\0'], [(socket.SOL_SOCKET, socket.SCM_CREDS, bytes(512))] + ) + else: + # Linux: no ancillary data needed, bus checks with SO_PEERCRED + await sock.send_all(b'\0') for req_data in authr: await sock.send_all(req_data) authr.feed(await sock.receive_some()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/low_level.py new/jeepney-0.9.0/jeepney/low_level.py --- old/jeepney-0.8.0/jeepney/low_level.py 2021-07-10 17:19:49.193029600 +0200 +++ new/jeepney-0.9.0/jeepney/low_level.py 2025-02-27 19:48:26.867029200 +0100 @@ -1,6 +1,7 @@ +import string +import struct from collections import deque from enum import Enum, IntEnum, IntFlag -import struct from typing import Optional class SizeLimitError(ValueError): @@ -156,9 +157,12 @@ assert buf[end:end + 1] == b'\0' return val, end + 1 - def serialise(self, data, pos, endianness, fds=None): + def check_data(self, data): if not isinstance(data, str): raise TypeError("Expected str, not {!r}".format(data)) + + def serialise(self, data, pos, endianness, fds=None): + self.check_data(data) encoded = data.encode('utf-8') len_data = self.length_type.serialise(len(encoded), pos, endianness) return len_data + encoded + b'\0' @@ -171,9 +175,28 @@ and (self.length_type == other.length_type) +class ObjectPathType(StringType): + def __init__(self): + super().__init__(simple_types['u']) + + def check_data(self, data): + super().check_data(data) + if not data.startswith('/'): + raise ValueError(f"Object path ({data!r}) must start with /") + if data.endswith('/') and len(data) > 1: + raise ValueError(f"Object path ({data!r}) cannot end with /") + if '//' in data: + raise ValueError(f"Object path ({data!r}) cannot contain double /") + valid_chars = string.ascii_letters + string.digits + '/_' + if any(c not in valid_chars for c in data): + raise ValueError( + f"Object path ({data!r}) can only contain A-Z, a-z, 0-9, / and _" + ) + + simple_types.update({ 's': StringType(simple_types['u']), # String - 'o': StringType(simple_types['u']), # Object path + 'o': ObjectPathType(), # Object path 'g': StringType(simple_types['y']), # Signature }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/routing.py new/jeepney-0.9.0/jeepney/routing.py --- old/jeepney-0.8.0/jeepney/routing.py 2021-07-10 17:28:19.764039500 +0200 +++ new/jeepney-0.9.0/jeepney/routing.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,76 +0,0 @@ -from warnings import warn - -from .low_level import MessageType, HeaderFields -from .wrappers import DBusErrorResponse - -class Router: - """Routing for messages coming back to a client application. - - :param handle_factory: Constructor for an object like asyncio.Future, - with methods *set_result* and *set_exception*. Outgoing method call - messages will get a handle associated with them. - :param on_unhandled: Callback for messages not otherwise dispatched. - """ - def __init__(self, handle_factory, on_unhandled=None): - self.handle_factory = handle_factory - self._on_unhandled = on_unhandled - self.outgoing_serial = 0 - self.awaiting_reply = {} - self.signal_callbacks = {} - - @property - def on_unhandled(self): - return self._on_unhandled - - @on_unhandled.setter - def on_unhandled(self, value): - warn("Setting on_unhandled is deprecated. Please use the filter() " - "method or simple receive() calls instead.", stacklevel=2) - self._on_unhandled = value - - def outgoing(self, msg): - """Set the serial number in the message & make a handle if a method call - """ - self.outgoing_serial += 1 - msg.header.serial = self.outgoing_serial - - if msg.header.message_type is MessageType.method_call: - self.awaiting_reply[msg.header.serial] = handle = self.handle_factory() - return handle - - def subscribe_signal(self, callback, path, interface, member): - """Add a callback for a signal. - """ - warn("The subscribe_signal() method is deprecated. " - "Please use the filter() API instead.", stacklevel=2) - self.signal_callbacks[(path, interface, member)] = callback - - def incoming(self, msg): - """Route an incoming message. - """ - hdr = msg.header - - # Signals: - if hdr.message_type is MessageType.signal: - key = (hdr.fields.get(HeaderFields.path, None), - hdr.fields.get(HeaderFields.interface, None), - hdr.fields.get(HeaderFields.member, None) - ) - cb = self.signal_callbacks.get(key, None) - if cb is not None: - cb(msg.body) - return - - # Method returns & errors - reply_serial = hdr.fields.get(HeaderFields.reply_serial, -1) - reply_handle = self.awaiting_reply.pop(reply_serial, None) - if reply_handle is not None: - if hdr.message_type is MessageType.method_return: - reply_handle.set_result(msg.body) - return - elif hdr.message_type is MessageType.error: - reply_handle.set_exception(DBusErrorResponse(msg)) - return - - if self.on_unhandled: - self.on_unhandled(msg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/tests/test_bus_messages.py new/jeepney-0.9.0/jeepney/tests/test_bus_messages.py --- old/jeepney-0.8.0/jeepney/tests/test_bus_messages.py 2021-07-10 17:19:49.194029600 +0200 +++ new/jeepney-0.9.0/jeepney/tests/test_bus_messages.py 2025-02-27 19:48:26.867029200 +0100 @@ -27,6 +27,9 @@ assert MatchRule(path_namespace='/org/freedesktop/portal').matches( new_signal(portal_req_iface, 'Response') ) + assert "/freedesktop/" in ( + MatchRule(path_namespace='/org/freedesktop/portal').serialise() + ) # Prefix but not a parent in the path hierarchy assert not MatchRule(path_namespace='/org/freedesktop/por').matches( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/tests/test_low_level.py new/jeepney-0.9.0/jeepney/tests/test_low_level.py --- old/jeepney-0.8.0/jeepney/tests/test_low_level.py 2018-09-11 21:07:32.036692000 +0200 +++ new/jeepney-0.9.0/jeepney/tests/test_low_level.py 2025-02-27 19:48:26.867029200 +0100 @@ -85,3 +85,17 @@ a.serialise(fake_list(100), 0, Endianness.little) with pytest.raises(SizeLimitError): a.serialise(fake_list(2**23 + 1), 0, Endianness.little) + + +def test_bad_object_path(): + with pytest.raises(ValueError): + ObjectPathType().check_data('org/freedesktop/DBus') + + with pytest.raises(ValueError): + ObjectPathType().check_data('/org/freedesktop/DBus/') + + with pytest.raises(ValueError): + ObjectPathType().check_data('/org//freedesktop/DBus') + + with pytest.raises(ValueError): + ObjectPathType().check_data('/org/freedesktop/DBüs') # Non-ASCII character diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/tests/test_routing.py new/jeepney-0.9.0/jeepney/tests/test_routing.py --- old/jeepney-0.8.0/jeepney/tests/test_routing.py 2017-05-29 13:45:52.322167000 +0200 +++ new/jeepney-0.9.0/jeepney/tests/test_routing.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,32 +0,0 @@ -from asyncio import Future -import pytest - -from jeepney.routing import Router -from jeepney.wrappers import new_method_return, new_error, DBusErrorResponse -from jeepney.bus_messages import message_bus - -def test_message_reply(): - router = Router(Future) - call = message_bus.Hello() - future = router.outgoing(call) - router.incoming(new_method_return(call, 's', ('test',))) - assert future.result() == ('test',) - -def test_error(): - router = Router(Future) - call = message_bus.Hello() - future = router.outgoing(call) - router.incoming(new_error(call, 'TestError', 'u', (31,))) - with pytest.raises(DBusErrorResponse) as e: - future.result() - assert e.value.name == 'TestError' - assert e.value.data == (31,) - -def test_unhandled(): - unhandled = [] - router = Router(Future, on_unhandled=unhandled.append) - msg = message_bus.Hello() - router.incoming(msg) - assert len(unhandled) == 1 - assert unhandled[0] == msg - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/tests/test_wrappers.py new/jeepney-0.9.0/jeepney/tests/test_wrappers.py --- old/jeepney-0.8.0/jeepney/tests/test_wrappers.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jeepney-0.9.0/jeepney/tests/test_wrappers.py 2025-02-27 19:48:26.867029200 +0100 @@ -0,0 +1,74 @@ +import pytest + +from jeepney.wrappers import * + +def test_bad_bus_name(): + obj = '/com/example/foo' + DBusAddress(obj, 'com.example.a') # Valid (well known name) + DBusAddress(obj, 'com.example.a-b') # Valid but discouraged + DBusAddress(obj, ':1.13') # Valid (unique name) + + with pytest.raises(ValueError, match='too long'): + DBusAddress(obj, 'com.example.' + ('a' * 256)) + + with pytest.raises(ValueError): + DBusAddress(obj, '.com.example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, 'com..example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, 'com.2example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, 'cöm.example.a') # Non-ASCII character + + with pytest.raises(ValueError): + DBusAddress(obj, 'com') + +def test_bad_interface(): + obj = '/com/example/foo' + busname = 'com.example.foo' + DBusAddress(obj, 'com.example.a', 'com.example.a_b') # Valid + + with pytest.raises(ValueError, match='too long'): + DBusAddress(obj, 'com.example.a', 'com.example.' + ('a' * 256)) + + with pytest.raises(ValueError): + DBusAddress(obj, 'com.example.a', 'com.example.a-b') # No hyphens + + with pytest.raises(ValueError): + DBusAddress(obj, busname, '.com.example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, busname, 'com..example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, busname, 'com.2example.a') + + with pytest.raises(ValueError): + DBusAddress(obj, busname, 'cöm.example.a') # Non-ASCII character + + with pytest.raises(ValueError): + DBusAddress(obj, busname, 'com') + + +def test_bad_member_name(): + addr = DBusAddress( + '/org/freedesktop/DBus', + bus_name='org.freedesktop.DBus', + interface='org.freedesktop.DBus', + ) + new_method_call(addr, 'Hello') + + with pytest.raises(ValueError, match='too long'): + new_method_call(addr, 'Hell' + ('o' * 256)) + + with pytest.raises(ValueError): + new_method_call(addr, 'org.Hello') + + with pytest.raises(ValueError): + new_method_call(addr, '9Hello') + + with pytest.raises(ValueError): + new_method_call(addr, '') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/jeepney/wrappers.py new/jeepney-0.9.0/jeepney/wrappers.py --- old/jeepney-0.8.0/jeepney/wrappers.py 2021-07-10 17:19:49.194029600 +0200 +++ new/jeepney-0.9.0/jeepney/wrappers.py 2025-02-27 19:48:26.867029200 +0100 @@ -1,3 +1,4 @@ +import re from typing import Union from warnings import warn @@ -15,6 +16,37 @@ 'DBusErrorResponse', ] +bus_name_pat = re.compile( + r'([A-Za-z_-][A-Za-z0-9_-]*(\.[A-Za-z_-][A-Za-z0-9_-]*)+' # Well known name + r'|:[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+))$', # Unique name +) + +def check_bus_name(name): + if len(name) > 255: + abbr = name[:8] + '...' + raise ValueError(f"Bus name ({abbr!r}) is too long (> 255 characters)") + if not bus_name_pat.match(name): + raise ValueError(f"Bus name ({name!r}) is not valid") + +interface_pat = re.compile(r'[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$') + +def check_interface(name): + if len(name) > 255: + abbr = name[:8] + '...' + raise ValueError(f"Interface name ({abbr!r}) is too long (> 255 characters)") + if not interface_pat.match(name): + raise ValueError(f"Interface name ({name!r}) is not valid") + +member_name_pat = re.compile(r'[A-Za-z_][A-Za-z0-9_]*$') + +def check_member_name(name): + if len(name) > 255: + abbr = name[:8] + '...' + raise ValueError(f"Member name ({abbr!r}) is too long (> 255 characters)") + if not member_name_pat.match(name): + raise ValueError(f"Member name ({name!r} is not valid") + + class DBusAddress: """This identifies the object and interface a message is for. @@ -25,8 +57,15 @@ interface='org.freedesktop.Notifications') """ def __init__(self, object_path, bus_name=None, interface=None): + ObjectPathType().check_data(object_path) self.object_path = object_path + + if bus_name is not None: + check_bus_name(bus_name) self.bus_name = bus_name + + if interface is not None: + check_interface(interface) self.interface = interface def __repr__(self): @@ -34,6 +73,7 @@ self.object_path, self.bus_name, self.interface) def with_interface(self, interface): + check_interface(interface) return type(self)(self.object_path, self.bus_name, interface) class DBusObject(DBusAddress): @@ -57,6 +97,7 @@ :param str signature: The DBus signature of the body data :param tuple body: Body data (i.e. method parameters) """ + check_member_name(method) header = new_header(MessageType.method_call) header.fields[HeaderFields.path] = remote_obj.object_path if remote_obj.bus_name is None: @@ -112,6 +153,7 @@ :param str signature: The DBus signature of the body data :param tuple body: Body data """ + check_member_name(signal) header = new_header(MessageType.signal) header.fields[HeaderFields.path] = emitter.object_path if emitter.interface is None: @@ -128,7 +170,14 @@ jeepney.bindgen can automatically create subclasses using introspection. """ + interface: Optional[str] = None + def __init__(self, object_path, bus_name): + ObjectPathType().check_data(object_path) + check_bus_name(bus_name) + if self.interface is not None: + check_interface(self.interface) + self.object_path = object_path self.bus_name = bus_name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jeepney-0.8.0/pyproject.toml new/jeepney-0.9.0/pyproject.toml --- old/jeepney-0.8.0/pyproject.toml 2022-04-03 19:31:55.797912400 +0200 +++ new/jeepney-0.9.0/pyproject.toml 2025-02-27 19:48:26.867029200 +0100 @@ -1,34 +1,39 @@ [build-system] -requires = ["flit_core >=2,<4"] +requires = ["flit_core >=3.11,<4"] build-backend = "flit_core.buildapi" -[tool.flit.metadata] -module = "jeepney" -author = "Thomas Kluyver" -author-email = "tho...@kluyver.me.uk" -home-page = "https://gitlab.com/takluyver/jeepney" -description-file = "README.rst" +[project] +name = "jeepney" +authors = [ + {name = "Thomas Kluyver", email = "tho...@kluyver.me.uk"}, +] +readme = "README.rst" requires-python = ">=3.7" +license = "MIT" +license-files = ["LICENSE"] classifiers = [ - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Topic :: Desktop Environment" ] +dynamic = ["version", "description"] -[tool.flit.metadata.requires-extra] +[project.optional-dependencies] test = [ "pytest", "pytest-trio", "pytest-asyncio >=0.17", "testpath", "trio", - "async-timeout", + "async-timeout; python_version < '3.11'", ] trio = [ "trio", - "async_generator; python_version == '3.6'", ] -[tool.flit.metadata.urls] +[project.urls] Documentation = "https://jeepney.readthedocs.io/en/latest/" +Source = "https://gitlab.com/takluyver/jeepney" +[tool.flit.sdist] +include = ["docs", "examples", "pytest.ini"] +exclude = ["docs/_build"]