Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: org.gnome.SessionManager RuntimeError (Issue 276) #278

Merged
merged 5 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ exclude_lines = [
"if (?:typing\\.)?TYPE_CHECKING:",
"@(abc\\.)?abstractmethod",
"@(typing\\.)?overload",
"raise NotImplementedError"
]

[tool.coverage.coverage_conditional_plugin.omit]
Expand Down
29 changes: 28 additions & 1 deletion src/wakepy/core/dbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def __repr__(self) -> str:
return f"<{self.method.service} {self.args} | bus: {self.method.bus}>"


class DBusAdapter:
class DBusAdapter: # pragma: no-cover-if-no-dbus
"""Defines the DBusAdapter interface. This is to be subclassed, and each
subclass is usually an implementation for a DBusAdapter using single
python (dbus-)library.
Expand All @@ -279,6 +279,10 @@ class DBusAdapter:
wakepy (Modes).
"""

def __init__(self) -> None:
# The values are DBusConnections. Type is defined by the used library.
self._connections: Dict[Optional[Union[str, BusType]], object] = dict()

def process(self, call: DBusMethodCall) -> object:
"""Processes a :class:`~wakepy.core.DBusMethodCall`.

Expand All @@ -290,6 +294,29 @@ def process(self, call: DBusMethodCall) -> object:
created within the :func:`~wakepy.DBusAdapter.process` call (this may
of course be cached)."""

def _get_connection(
self, bus: Optional[Union[str, BusType]] = BusType.SESSION
) -> object:
"""Gets either a new connection or a cached one, if there is such.
Caching of connections is done on bus level."""

if bus in self._connections:
return self._connections[bus]

connection = self._create_new_connection(bus)

self._connections[bus] = connection
return connection

def _create_new_connection(
self, bus: Optional[Union[str, BusType]] = BusType.SESSION
) -> object:
"""Create a new Dbus connection for a bus using the library of choice.
For example, when creating DBusAdapter subclass for jeepney, could
return an instance of ``jeepney.io.blocking.DBusConnection``.
"""
raise NotImplementedError("Implement in subclass")


def get_dbus_adapter(
dbus_adapter: Optional[Type[DBusAdapter] | DBusAdapterTypeSeq] = None,
Expand Down
27 changes: 22 additions & 5 deletions src/wakepy/dbus_adapters/jeepney.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
from __future__ import annotations

import typing
from typing import cast

from jeepney import DBusAddress, new_method_call
from jeepney.io.blocking import open_dbus_connection
from jeepney.io.blocking import DBusConnection, open_dbus_connection
from jeepney.wrappers import unwrap_msg

from wakepy.core import DBusAdapter, DBusMethodCall
from wakepy.core.dbus import BusType

if typing.TYPE_CHECKING:
from typing import Optional, Union

TypeOfBus = Optional[Union[BusType, str]]


class DBusNotFoundError(RuntimeError): ...
Expand Down Expand Up @@ -30,8 +41,17 @@ def process(self, call: DBusMethodCall) -> object:
signature=call.method.signature,
body=call.args,
)

connection = cast(DBusConnection, self._get_connection(call.method.bus)) # type: ignore[no-any-unimported]
reply = connection.send_and_get_reply(msg, timeout=self.timeout)
resp = unwrap_msg(reply)
return resp

def _create_new_connection( # type: ignore[no-any-unimported]
self, bus: TypeOfBus = BusType.SESSION
) -> DBusConnection:
try:
connection = open_dbus_connection(bus=call.method.bus)
return open_dbus_connection(bus=bus)
except KeyError as exc:
if "DBUS_SESSION_BUS_ADDRESS" in str(exc):
raise DBusNotFoundError(
Expand All @@ -44,6 +64,3 @@ def process(self, call: DBusMethodCall) -> object:
) from exc
else:
raise
reply = connection.send_and_get_reply(msg, timeout=self.timeout)
resp = unwrap_msg(reply)
return resp
3 changes: 2 additions & 1 deletion tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def test(c, pdb: bool = False) -> None:
run = get_run_with_print(c)
pdb_flag = " --pdb " if pdb else ""
res = run(
f"python -m pytest {pdb_flag}--cov-branch --cov wakepy --cov-fail-under=100",
f"env -u DBUS_SESSION_BUS_ADDRESS python -m pytest {pdb_flag}--cov-branch "
"--cov wakepy --cov-fail-under=100",
ignore_errors=True,
)
if res.exited:
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import subprocess
import sys
import warnings

import pytest

Expand All @@ -32,7 +33,11 @@ def gc_collect_after_dbus_integration_tests():
# this as garbage colletion is triggered also automatically. The garbage
# collection must be triggered here manually as the warnings are
# ResourceWarning is only filtered away in the dbus integration tests.
gc.collect()

with warnings.catch_warnings():
warnings.simplefilter("ignore")
gc.collect()

logger.debug("called gc.collect")


Expand Down
8 changes: 8 additions & 0 deletions tests/integration/test_dbus_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,11 @@ def test_random_keyerror_on_connection_creation(
match="Some other reason",
):
self.adapter.process(self.call)


def test_jeepney_adapter_caching(private_bus: str):
adapter = JeepneyDBusAdapter()
con = adapter._get_connection(private_bus)

# Call again with same bus name -> get same (cached) connection.
assert adapter._get_connection(private_bus) is con
2 changes: 0 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ minversion = 4.8.0
[testenv]
description = run the tests with pytest
deps = -r{toxinidir}/requirements/requirements-test.txt
passenv =
DBUS_SESSION_BUS_ADDRESS
commands =
; -W error: turn warnings into errors
{envpython} -m pytest -W error {tty:--color=yes} \
Expand Down