Skip to content

Commit

Permalink
Change how platform support is defined
Browse files Browse the repository at this point in the history
  • Loading branch information
fohrloop committed Sep 13, 2024
1 parent 19bad39 commit 962bdc0
Show file tree
Hide file tree
Showing 19 changed files with 289 additions and 121 deletions.
1 change: 1 addition & 0 deletions src/wakepy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def handle_activation_error(result: ActivationResult) -> None:
def _get_activation_error_text(result: ActivationResult) -> str:
from wakepy import __version__

# LATER: This should be improved in https://github.com/fohrloop/wakepy/issues/378
error_text = f"""
Wakepy could not activate the "{result.mode_name}" mode. This might occur because of a bug or because your current platform is not yet supported or your system is missing required software.
Expand Down
3 changes: 2 additions & 1 deletion src/wakepy/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from .activationresult import ActivationResult as ActivationResult
from .activationresult import MethodActivationResult as MethodActivationResult
from .constants import BusType as BusType
from .constants import IdentifiedPlatformType as IdentifiedPlatformType
from .constants import ModeName as ModeName
from .constants import PlatformName as PlatformName
from .constants import PlatformType as PlatformType
from .dbus import DBusAdapter as DBusAdapter
from .dbus import DBusAddress as DBusAddress
from .dbus import DBusMethod as DBusMethod
Expand Down
50 changes: 42 additions & 8 deletions src/wakepy/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,56 @@
"""


class PlatformName(StrEnum):
"""All the different platforms wakepy knows about. Any platform that is not
detected will be named ``OTHER``."""
class IdentifiedPlatformType(StrEnum):
"""All the different platforms wakepy knows about. Any process will
categorized into exactly one IdentifiedPlatformType type; these are
mutually exclusive options. Any platform that is not detected will be
labeled as ``UNKNOWN``.
WINDOWS = auto()
TODO: Add link to the docs
See also: PlatformType, which you should use with Method subclasses."""

WINDOWS = auto()
LINUX = auto()

MACOS = auto()
FREEBSD = auto()
UNKNOWN = auto()


# TODO: Add to docs
# TODO: test that each IdentifiedPlatformType is in PlatformType
class PlatformType(StrEnum):
"""Enumeration for supported platform types. Each identified platform can
be categorized to at least one, but potentially many of these. In other
words, each IdentifiedPlatformType maps into one or many PlatformTypes.
To be used in subclasses of Method in the supported_platforms."""

WINDOWS = IdentifiedPlatformType.WINDOWS.value
"""Any Windows version from Windows10 onwards."""

LINUX = IdentifiedPlatformType.LINUX.value
"""Includes any Linux distro. Excludes things like Android & ChromeOS"""

MACOS = IdentifiedPlatformType.MACOS.value
"""Mac OS (Darwin)"""

OTHER = auto()
"""Anything else"""
FREEBSD = IdentifiedPlatformType.FREEBSD.value
"""Also includes GhostBSD"""

UNKNOWN = IdentifiedPlatformType.UNKNOWN.value
"""Any non-identified platform"""

BSD = auto()
"""Any BSD system (Currently just FreeBSD / GhostBSD, but is likely to
change in the future)."""

UNIX_LIKE_FOSS = auto()
"""Unix-like desktop environment, but FOSS. Includes: Linux and BSD.
Excludes: Android (mobile), MacOS (non-FOSS), ChromeOS (non-FOSS)."""

PlatformNameValue = Literal["WINDOWS", "LINUX", "MACOS", "OTHER"]
ANY = auto()
"""Means any platform."""


class ModeName(StrEnum):
Expand Down
54 changes: 25 additions & 29 deletions src/wakepy/core/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from typing import Type, cast

from .activationresult import MethodActivationResult
from .constants import PlatformName, StageName
from .constants import PlatformType, StageName
from .heartbeat import Heartbeat
from .platform import CURRENT_PLATFORM
from .platform import CURRENT_PLATFORM, get_platform_supported
from .registry import register_method
from .strenum import StrEnum, auto

Expand All @@ -31,7 +31,7 @@

from wakepy.core import DBusAdapter, DBusMethodCall

from .constants import ModeName, PlatformName
from .constants import ModeName, PlatformType

MethodCls = Type["Method"]

Expand Down Expand Up @@ -69,13 +69,10 @@ class Method(ABC):
defines the Mode `foo` (:class:`Mode` classes are themselves not defined or
registered anywhere)"""

supported_platforms: Tuple[PlatformName, ...] = (
PlatformName.LINUX,
PlatformName.WINDOWS,
PlatformName.MACOS,
PlatformName.OTHER,
)
"""Lists the platforms the Method supports. If a platform is not listed in
supported_platforms: Tuple[PlatformType, ...] = (PlatformType.ANY,)
"""TODO: UPDATE THIS.
Lists the platforms the Method supports. If a platform is not listed in
``method.supported_platforms``, the ``method`` is not going to be used on
the platform (when used as part of a :class:`Mode`), and the Method
activation result will show a fail in the "PLATFORM" stage.
Expand Down Expand Up @@ -114,6 +111,7 @@ def __init__(self, **kwargs: object) -> None:

# waits for https://github.com/fohrloop/wakepy/issues/256
# self.method_kwargs = kwargs
_check_supported_platforms(self.supported_platforms, self.__class__.__name__)

def __init_subclass__(cls, **kwargs: object) -> None:
register_method(cls)
Expand Down Expand Up @@ -285,6 +283,22 @@ def is_unnamed(cls) -> bool:
return cls.name == unnamed


def _check_supported_platforms(
supported_platforms: Tuple[PlatformType, ...], classname: str
) -> None:
err_supported_platforms = (
f"The supported_platforms of {classname} must be a tuple of PlatformType!"
)

if not isinstance(supported_platforms, tuple):
raise ValueError(err_supported_platforms)
for p in supported_platforms:
if not isinstance(p, PlatformType):
raise ValueError(
err_supported_platforms + f' One item ({p}) is of type "{type(p)}"'
)


def activate_method(method: Method) -> Tuple[MethodActivationResult, Heartbeat | None]:
"""Activates a mode defined by a single Method.
Expand All @@ -303,7 +317,7 @@ def activate_method(method: Method) -> Tuple[MethodActivationResult, Heartbeat |
success=False, method_name=method.name, mode_name=method.mode_name
)

if not get_platform_supported(method, platform=CURRENT_PLATFORM):
if not get_platform_supported(CURRENT_PLATFORM, method.supported_platforms):
result.failure_stage = StageName.PLATFORM_SUPPORT
return result, None

Expand Down Expand Up @@ -372,24 +386,6 @@ def deactivate_method(method: Method, heartbeat: Optional[Heartbeat] = None) ->
method.dbus_adapter.close_connections()


def get_platform_supported(method: Method, platform: PlatformName) -> bool:
"""Checks if method is supported by the platform
Parameters
----------
method: Method
The method which platform support to check.
platform:
The platform to check against.
Returns
-------
is_supported: bool
If True, the platform is supported. Otherwise, False.
"""
return platform in method.supported_platforms


def caniuse_fails(method: Method) -> tuple[bool, str]:
"""Check if the requirements of a Method are met or not.
Expand Down
94 changes: 87 additions & 7 deletions src/wakepy/core/platform.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,103 @@
from __future__ import annotations

import platform
import typing
import warnings

from .constants import PlatformName
from .constants import IdentifiedPlatformType, PlatformType

if typing.TYPE_CHECKING:
from typing import Callable, Tuple

PlatformFunc = Callable[[IdentifiedPlatformType], bool]


def get_current_platform() -> PlatformName:
def get_current_platform() -> IdentifiedPlatformType:
# Ref: https://docs.python.org/3/library/platform.html#platform.system
system = platform.system()
if system == "Windows":
return PlatformName.WINDOWS
return IdentifiedPlatformType.WINDOWS
elif system == "Darwin":
return PlatformName.MACOS
return IdentifiedPlatformType.MACOS
elif system == "Linux":
return PlatformName.LINUX
return IdentifiedPlatformType.LINUX
elif system == "FreeBSD":
return IdentifiedPlatformType.FREEBSD

# LATER: This should be improved in https://github.com/fohrloop/wakepy/issues/378
warnings.warn(
f"Could not detect current platform! platform.system() returned {system}"
)
return PlatformName.OTHER
return IdentifiedPlatformType.UNKNOWN


CURRENT_PLATFORM: IdentifiedPlatformType = get_current_platform()


def get_platform_supported(
platform: IdentifiedPlatformType, supported_platforms: Tuple[PlatformType, ...]
) -> bool | None:
"""Checks if method is supported by the platform
TODO: Update the docstring.
Parameters
----------
platform: Method
The method which platform support to check.
supported_platforms:
The platform to check against.
Returns
-------
is_supported: bool
If platform is supported, returns True. If the support is unknown,
returns None, and if the platform is not supported, returns False.
"""
for supported_platform in supported_platforms:
func = PLATFORM_INFO_FUNCS[supported_platform]
if func(platform) is True:
return True
if is_unknown(platform):
return None
return False


def is_windows(current_platform: IdentifiedPlatformType) -> bool:
return current_platform == IdentifiedPlatformType.WINDOWS


def is_linux(current_platform: IdentifiedPlatformType) -> bool:
return current_platform == IdentifiedPlatformType.LINUX


def is_freebsd(current_platform: IdentifiedPlatformType) -> bool:
return current_platform == IdentifiedPlatformType.FREEBSD


def is_macos(current_platform: IdentifiedPlatformType) -> bool:
return current_platform == IdentifiedPlatformType.MACOS


def is_bsd(current_platform: IdentifiedPlatformType) -> bool:
return is_freebsd(current_platform)


def is_unknown(current_platform: IdentifiedPlatformType) -> bool:
return current_platform == IdentifiedPlatformType.UNKNOWN


def is_unix_like_foss(current_platform: IdentifiedPlatformType) -> bool:
return is_bsd(current_platform) or is_linux(current_platform)


CURRENT_PLATFORM = get_current_platform()
PLATFORM_INFO_FUNCS: dict[PlatformType, PlatformFunc] = {
PlatformType.WINDOWS: is_windows,
PlatformType.LINUX: is_linux,
PlatformType.MACOS: is_macos,
PlatformType.FREEBSD: is_freebsd,
PlatformType.UNKNOWN: is_unknown,
PlatformType.BSD: is_bsd,
PlatformType.ANY: lambda _: True,
PlatformType.UNIX_LIKE_FOSS: is_unix_like_foss,
}
4 changes: 2 additions & 2 deletions src/wakepy/methods/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
activation success. It is controlled with the WAKEPY_FAKE_SUCCESS environment
variable and meant to be used in CI pipelines / tests."""

from wakepy.core import CURRENT_PLATFORM, Method
from wakepy.core import Method, PlatformType
from wakepy.core.constants import WAKEPY_FAKE_SUCCESS


Expand All @@ -16,7 +16,7 @@ class WakepyFakeSuccess(Method):

name = WAKEPY_FAKE_SUCCESS
mode_name = "_fake"
supported_platforms = (CURRENT_PLATFORM,)
supported_platforms = (PlatformType.ANY,)

def enter_mode(self) -> None:
"""Does nothing ("succeeds" automatically; Will never raise an
Expand Down
4 changes: 2 additions & 2 deletions src/wakepy/methods/freedesktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
DBusMethodCall,
Method,
ModeName,
PlatformName,
PlatformType,
)

if typing.TYPE_CHECKING:
Expand All @@ -41,7 +41,7 @@ class FreedesktopInhibitorWithCookieMethod(Method):
"""Base class for freedesktop.org D-Bus based methods."""

service_dbus_address: DBusAddress
supported_platforms = (PlatformName.LINUX,)
supported_platforms = (PlatformType.LINUX,)

def __init__(self, **kwargs: object) -> None:
super().__init__(**kwargs)
Expand Down
4 changes: 2 additions & 2 deletions src/wakepy/methods/gnome.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
DBusMethodCall,
Method,
ModeName,
PlatformName,
PlatformType,
)

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -56,7 +56,7 @@ class _GnomeSessionManager(Method, ABC):
params=("inhibit_cookie",),
).of(session_manager)

supported_platforms = (PlatformName.LINUX,)
supported_platforms = (PlatformType.LINUX,)

@property
@abstractmethod
Expand Down
4 changes: 2 additions & 2 deletions src/wakepy/methods/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from abc import ABC, abstractmethod
from subprocess import PIPE, Popen

from wakepy.core import Method, ModeName, PlatformName
from wakepy.core import Method, ModeName, PlatformType

if typing.TYPE_CHECKING:
from typing import Optional
Expand All @@ -18,7 +18,7 @@ class _MacCaffeinate(Method, ABC):
Also: https://web.archive.org/web/20140604153141/https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/caffeinate.8.html
"""

supported_platforms = (PlatformName.MACOS,)
supported_platforms = (PlatformType.MACOS,)

def __init__(self, **kwargs: object) -> None:
super().__init__(**kwargs)
Expand Down
4 changes: 2 additions & 2 deletions src/wakepy/methods/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from queue import Queue
from threading import Event, Thread

from wakepy.core import Method, ModeName, PlatformName
from wakepy.core import Method, ModeName, PlatformType

logger = logging.getLogger(__name__)

Expand All @@ -34,7 +34,7 @@ class WindowsSetThreadExecutionState(Method, ABC):

# The SetThreadExecutionState docs say that it supports Windows XP and
# above (client) or Windows Server 2003 and above (server)
supported_platforms = (PlatformName.WINDOWS,)
supported_platforms = (PlatformType.WINDOWS,)
_wait_timeout = 5 # seconds
"""timeout for calls like queue.get() and thread.join() which could
otherwise block execution indefinitely."""
Expand Down
Loading

0 comments on commit 962bdc0

Please sign in to comment.