Compare commits

...

5 Commits
0.46 ... 0.47

Author SHA1 Message Date
Romuald Juchnowicz-Bierbasz
cec36695b6 Increment version 2019-08-13 16:08:59 +02:00
Romuald Juchnowicz-Bierbasz
4cc8be8f5d SDK-2997: Add launch_platform_client method 2019-08-13 15:23:20 +02:00
Vadim Suharnikov
f5eb32aa19 Fix a comment typo 2019-08-04 01:06:22 +02:00
Romuald Bierbasz
a76345ff6b Docs fixes 2019-08-02 17:23:56 +02:00
Romuald Bierbasz
c3bbeee54d Turn on type checking 2019-08-02 15:15:29 +02:00
14 changed files with 75 additions and 40 deletions

View File

@@ -47,7 +47,7 @@ templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path. # This pattern also affects html_static_path and html_extra_path.
exclude_patterns = [] exclude_patterns = [] # type: ignore
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------

2
mypy.ini Normal file
View File

@@ -0,0 +1,2 @@
[mypy]
ignore_missing_imports = True

View File

@@ -1,2 +1,2 @@
[pytest] [pytest]
addopts = --flakes addopts = --flakes --mypy

View File

@@ -2,6 +2,7 @@
pytest==4.2.0 pytest==4.2.0
pytest-asyncio==0.10.0 pytest-asyncio==0.10.0
pytest-mock==1.10.3 pytest-mock==1.10.3
pytest-mypy==0.3.2
pytest-flakes==4.0.0 pytest-flakes==4.0.0
# because of pip bug https://github.com/pypa/pip/issues/4780 # because of pip bug https://github.com/pypa/pip/issues/4780
aiohttp==3.5.4 aiohttp==3.5.4

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="galaxy.plugin.api", name="galaxy.plugin.api",
version="0.46", version="0.47",
description="GOG Galaxy Integrations Python API", description="GOG Galaxy Integrations Python API",
author='Galaxy team', author='Galaxy team',
author_email='galaxy@gog.com', author_email='galaxy@gog.com',

View File

@@ -1 +1 @@
__path__ = __import__('pkgutil').extend_path(__path__, __name__) __path__: str = __import__('pkgutil').extend_path(__path__, __name__)

View File

@@ -99,6 +99,7 @@ class Feature(Enum):
VerifyGame = "VerifyGame" VerifyGame = "VerifyGame"
ImportFriends = "ImportFriends" ImportFriends = "ImportFriends"
ShutdownPlatformClient = "ShutdownPlatformClient" ShutdownPlatformClient = "ShutdownPlatformClient"
LaunchPlatformClient = "LaunchPlatformClient"
class LicenseType(Enum): class LicenseType(Enum):

View File

@@ -1,6 +1,6 @@
from galaxy.api.jsonrpc import ApplicationError, UnknownError from galaxy.api.jsonrpc import ApplicationError, UnknownError
UnknownError = UnknownError assert UnknownError
class AuthenticationRequired(ApplicationError): class AuthenticationRequired(ApplicationError):
def __init__(self, data=None): def __init__(self, data=None):

View File

@@ -107,6 +107,9 @@ class Plugin:
self._register_notification("shutdown_platform_client", self.shutdown_platform_client) self._register_notification("shutdown_platform_client", self.shutdown_platform_client)
self._detect_feature(Feature.ShutdownPlatformClient, ["shutdown_platform_client"]) self._detect_feature(Feature.ShutdownPlatformClient, ["shutdown_platform_client"])
self._register_notification("launch_platform_client", self.launch_platform_client)
self._detect_feature(Feature.LaunchPlatformClient, ["launch_platform_client"])
self._register_method("import_friends", self.get_friends, result_name="friend_info_list") self._register_method("import_friends", self.get_friends, result_name="friend_info_list")
self._detect_feature(Feature.ImportFriends, ["get_friends"]) self._detect_feature(Feature.ImportFriends, ["get_friends"])
@@ -270,7 +273,7 @@ class Plugin:
"""Notify the client to remove game from the list of owned games """Notify the client to remove game from the list of owned games
of the currently authenticated user. of the currently authenticated user.
:param game_id: game id of the game to remove from the list of owned games :param game_id: the id of the game to remove from the list of owned games
Example use case of remove_game: Example use case of remove_game:
@@ -300,7 +303,7 @@ class Plugin:
def unlock_achievement(self, game_id: str, achievement: Achievement) -> None: def unlock_achievement(self, game_id: str, achievement: Achievement) -> None:
"""Notify the client to unlock an achievement for a specific game. """Notify the client to unlock an achievement for a specific game.
:param game_id: game_id of the game for which to unlock an achievement. :param game_id: the id of the game for which to unlock an achievement.
:param achievement: achievement to unlock. :param achievement: achievement to unlock.
""" """
params = { params = {
@@ -552,9 +555,11 @@ class Plugin:
async def prepare_achievements_context(self, game_ids: List[str]) -> Any: async def prepare_achievements_context(self, game_ids: List[str]) -> Any:
"""Override this method to prepare context for get_unlocked_achievements. """Override this method to prepare context for get_unlocked_achievements.
This allows for optimizations like batch requests to platform API. This allows for optimizations like batch requests to platform API.
Default implementation returns None. Default implementation returns None.
:param game_ids: the ids of the games for which achievements are imported
:return: context
""" """
return None return None
@@ -563,9 +568,9 @@ class Plugin:
for the game identified by the provided game_id. for the game identified by the provided game_id.
This method is called by import task initialized by GOG Galaxy Client. This method is called by import task initialized by GOG Galaxy Client.
:param game_id: :param game_id: the id of the game for which the achievements are returned
:param context: Value return from :meth:`prepare_achievements_context` :param context: the value returned from :meth:`prepare_achievements_context`
:return: :return: list of Achievement objects
""" """
raise NotImplementedError() raise NotImplementedError()
@@ -601,7 +606,7 @@ class Plugin:
identified by the provided game_id. identified by the provided game_id.
This method is called by the GOG Galaxy Client. This method is called by the GOG Galaxy Client.
:param str game_id: id of the game to launch :param str game_id: the id of the game to launch
Example of possible override of the method: Example of possible override of the method:
@@ -619,7 +624,7 @@ class Plugin:
identified by the provided game_id. identified by the provided game_id.
This method is called by the GOG Galaxy Client. This method is called by the GOG Galaxy Client.
:param str game_id: id of the game to install :param str game_id: the id of the game to install
Example of possible override of the method: Example of possible override of the method:
@@ -637,7 +642,7 @@ class Plugin:
identified by the provided game_id. identified by the provided game_id.
This method is called by the GOG Galaxy Client. This method is called by the GOG Galaxy Client.
:param str game_id: id of the game to uninstall :param str game_id: the id of the game to uninstall
Example of possible override of the method: Example of possible override of the method:
@@ -655,6 +660,11 @@ class Plugin:
This method is called by the GOG Galaxy Client.""" This method is called by the GOG Galaxy Client."""
raise NotImplementedError() raise NotImplementedError()
async def launch_platform_client(self) -> None:
"""Override this method to launch platform client.
This method is called by the GOG Galaxy Client."""
raise NotImplementedError()
async def get_friends(self) -> List[FriendInfo]: async def get_friends(self) -> List[FriendInfo]:
"""Override this method to return the friends list """Override this method to return the friends list
of the currently authenticated user. of the currently authenticated user.
@@ -707,6 +717,9 @@ class Plugin:
"""Override this method to prepare context for get_game_time. """Override this method to prepare context for get_game_time.
This allows for optimizations like batch requests to platform API. This allows for optimizations like batch requests to platform API.
Default implementation returns None. Default implementation returns None.
:param game_ids: the ids of the games for which game time are imported
:return: context
""" """
return None return None
@@ -715,13 +728,13 @@ class Plugin:
identified by the provided game_id. identified by the provided game_id.
This method is called by import task initialized by GOG Galaxy Client. This method is called by import task initialized by GOG Galaxy Client.
:param game_id: :param game_id: the id of the game for which the game time is returned
:param context: Value return from :meth:`prepare_game_times_context` :param context: the value returned from :meth:`prepare_game_times_context`
:return: :return: GameTime object
""" """
raise NotImplementedError() raise NotImplementedError()
def game_times_import_complete(self): def game_times_import_complete(self) -> None:
"""Override this method to handle operations after game times import is finished """Override this method to handle operations after game times import is finished
(like updating cache). (like updating cache).
""" """

View File

@@ -1,11 +1,8 @@
import platform import sys
from dataclasses import dataclass from dataclasses import dataclass
from typing import Iterable, NewType, Optional, Set from typing import Iterable, NewType, Optional, List, cast
def is_windows():
return platform.system() == "Windows"
ProcessId = NewType("ProcessId", int) ProcessId = NewType("ProcessId", int)
@@ -16,7 +13,7 @@ class ProcessInfo:
binary_path: Optional[str] binary_path: Optional[str]
if is_windows(): if sys.platform == "win32":
from ctypes import byref, sizeof, windll, create_unicode_buffer, FormatError, WinError from ctypes import byref, sizeof, windll, create_unicode_buffer, FormatError, WinError
from ctypes.wintypes import DWORD from ctypes.wintypes import DWORD
@@ -25,14 +22,14 @@ if is_windows():
_PROC_ID_T = DWORD _PROC_ID_T = DWORD
list_size = 4096 list_size = 4096
def try_get_pids(list_size: int) -> Set[ProcessId]: def try_get_pids(list_size: int) -> List[ProcessId]:
result_size = DWORD() result_size = DWORD()
proc_id_list = (_PROC_ID_T * list_size)() proc_id_list = (_PROC_ID_T * list_size)()
if not windll.psapi.EnumProcesses(byref(proc_id_list), sizeof(proc_id_list), byref(result_size)): if not windll.psapi.EnumProcesses(byref(proc_id_list), sizeof(proc_id_list), byref(result_size)):
raise WinError(descr="Failed to get process ID list: %s" % FormatError()) raise WinError(descr="Failed to get process ID list: %s" % FormatError()) # type: ignore
return proc_id_list[:int(result_size.value / sizeof(_PROC_ID_T()))] return cast(List[ProcessId], proc_id_list[:int(result_size.value / sizeof(_PROC_ID_T()))])
while True: while True:
proc_ids = try_get_pids(list_size) proc_ids = try_get_pids(list_size)
@@ -59,7 +56,7 @@ if is_windows():
exe_path_buffer = create_unicode_buffer(_MAX_PATH) exe_path_buffer = create_unicode_buffer(_MAX_PATH)
exe_path_len = DWORD(len(exe_path_buffer)) exe_path_len = DWORD(len(exe_path_buffer))
return exe_path_buffer[:exe_path_len.value] if windll.kernel32.QueryFullProcessImageNameW( return cast(str, exe_path_buffer[:exe_path_len.value]) if windll.kernel32.QueryFullProcessImageNameW(
h_process, _WIN32_PATH_FORMAT, exe_path_buffer, byref(exe_path_len) h_process, _WIN32_PATH_FORMAT, exe_path_buffer, byref(exe_path_len)
) else None ) else None
@@ -86,6 +83,6 @@ else:
return process_info return process_info
def process_iter() -> Iterable[ProcessInfo]: def process_iter() -> Iterable[Optional[ProcessInfo]]:
for pid in pids(): for pid in pids():
yield get_process_info(pid) yield get_process_info(pid)

View File

@@ -40,6 +40,7 @@ def plugin(reader, writer):
"achievements_import_complete", "achievements_import_complete",
"get_local_games", "get_local_games",
"launch_game", "launch_game",
"launch_platform_client",
"install_game", "install_game",
"uninstall_game", "uninstall_game",
"get_friends", "get_friends",

View File

@@ -13,7 +13,8 @@ def test_base_class():
Feature.ImportAchievements, Feature.ImportAchievements,
Feature.ImportGameTime, Feature.ImportGameTime,
Feature.ImportFriends, Feature.ImportFriends,
Feature.ShutdownPlatformClient Feature.ShutdownPlatformClient,
Feature.LaunchPlatformClient
} }

View File

@@ -3,6 +3,8 @@ from http import HTTPStatus
import aiohttp import aiohttp
import pytest import pytest
from multidict import CIMultiDict, CIMultiDictProxy
from yarl import URL
from galaxy.api.errors import ( from galaxy.api.errors import (
AccessDenied, AuthenticationRequired, BackendTimeout, BackendNotAvailable, BackendError, NetworkError, AccessDenied, AuthenticationRequired, BackendTimeout, BackendNotAvailable, BackendError, NetworkError,
@@ -10,7 +12,7 @@ from galaxy.api.errors import (
) )
from galaxy.http import handle_exception from galaxy.http import handle_exception
request_info = aiohttp.RequestInfo("http://o.pl", "GET", {}) request_info = aiohttp.RequestInfo(URL("http://o.pl"), "GET", CIMultiDictProxy(CIMultiDict()))
@pytest.mark.parametrize( @pytest.mark.parametrize(
"aiohttp_exception,expected_exception_type", "aiohttp_exception,expected_exception_type",
@@ -18,15 +20,15 @@ request_info = aiohttp.RequestInfo("http://o.pl", "GET", {})
(asyncio.TimeoutError(), BackendTimeout), (asyncio.TimeoutError(), BackendTimeout),
(aiohttp.ServerDisconnectedError(), BackendNotAvailable), (aiohttp.ServerDisconnectedError(), BackendNotAvailable),
(aiohttp.ClientConnectionError(), NetworkError), (aiohttp.ClientConnectionError(), NetworkError),
(aiohttp.ContentTypeError(request_info, []), UnknownBackendResponse), (aiohttp.ContentTypeError(request_info, ()), UnknownBackendResponse),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.UNAUTHORIZED), AuthenticationRequired), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.UNAUTHORIZED), AuthenticationRequired),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.FORBIDDEN), AccessDenied), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.FORBIDDEN), AccessDenied),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.SERVICE_UNAVAILABLE), BackendNotAvailable), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.SERVICE_UNAVAILABLE), BackendNotAvailable),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.TOO_MANY_REQUESTS), TooManyRequests), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.TOO_MANY_REQUESTS), TooManyRequests),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.INTERNAL_SERVER_ERROR), BackendError), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.INTERNAL_SERVER_ERROR), BackendError),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.NOT_IMPLEMENTED), BackendError), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.NOT_IMPLEMENTED), BackendError),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.BAD_REQUEST), UnknownError), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.BAD_REQUEST), UnknownError),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.NOT_FOUND), UnknownError), (aiohttp.ClientResponseError(request_info, (), status=HTTPStatus.NOT_FOUND), UnknownError),
(aiohttp.ClientError(), UnknownError) (aiohttp.ClientError(), UnknownError)
] ]
) )

View File

@@ -0,0 +1,17 @@
import pytest
from galaxy.unittest.mock import async_return_value
from tests import create_message
@pytest.mark.asyncio
async def test_success(plugin, read):
request = {
"jsonrpc": "2.0",
"method": "launch_platform_client"
}
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"")]
plugin.launch_platform_client.return_value = async_return_value(None)
await plugin.run()
plugin.launch_platform_client.assert_called_with()