Compare commits

..

22 Commits
0.40.1 ... 0.41

Author SHA1 Message Date
Romuald Juchnowicz-Bierbasz
630d878a3c Use constants 2019-07-12 11:46:48 +02:00
Romuald Juchnowicz-Bierbasz
cc63c24bde Increment version 2019-07-12 11:46:48 +02:00
Romuald Juchnowicz-Bierbasz
0d0f657240 SDK-2930: Refactor http module 2019-07-12 11:46:48 +02:00
Steven M. Vascellaro
33c630225d README.md cleanup (#17) 2019-07-12 10:39:37 +02:00
apaulouski
f4bd18a8ab Merge pull request #2 from rogersachan/rogersachan-patch-1
Fix spelling errors in the README
2019-07-10 12:12:07 +02:00
Roger
fa4541434f Merge branch 'master' into rogersachan-patch-1 2019-07-09 13:30:35 -04:00
rbierbasz-gog
c083a3089a Create .travis.yml 2019-07-08 15:43:12 +02:00
Romuald Juchnowicz-Bierbasz
f6b5a12b24 SDK-2932: Remove github deployment (use mirroring) 2019-07-03 13:42:06 +02:00
Romuald Juchnowicz-Bierbasz
8a67747df5 Merge remote-tracking branch 'github/master'
* github/master:
  version 0.38
  version 0.35.2
  version 0.35.1
  version 0.34
  version 0.33.1
  version 0.33
  version 0.32.1
  version 0.32.0
  version 0.31.3
  version 0.31.2
  version 0.31.1
  Initial commit
2019-07-03 13:35:34 +02:00
GOG Galaxy SDK Team
f1fd00fcd3 version 0.38 2019-06-26 12:46:23 +02:00
Roger
1edf4ff5ba Fix spelling errors 2019-06-19 15:39:22 -04:00
GOG Galaxy SDK Team
9d5d48032e version 0.35.2 2019-06-17 18:11:24 +02:00
GOG Galaxy SDK Team
179fd147c1 version 0.35.1 2019-06-17 17:41:44 +02:00
GOG Galaxy SDK Team
7789927ed9 version 0.34 2019-06-14 16:54:11 +02:00
GOG Galaxy SDK Team
e2f26271cb version 0.33.1 2019-06-14 14:49:25 +02:00
GOG Galaxy SDK Team
3bd0b71ab3 version 0.33 2019-06-13 12:30:12 +02:00
GOG Galaxy SDK Team
192d655d51 version 0.32.1 2019-06-10 19:04:08 +02:00
GOG Galaxy SDK Team
6c6dc42cd6 version 0.32.0 2019-06-07 15:08:47 +02:00
GOG Galaxy SDK Team
f97b6c8971 version 0.31.3 2019-05-31 12:09:18 +02:00
GOG Galaxy SDK Team
0af7387342 version 0.31.2 2019-05-31 11:53:15 +02:00
GOG Galaxy SDK Team
60fab25a55 version 0.31.1 2019-05-29 13:09:13 +02:00
GOG Galaxy SDK Team
6f717a1e31 Initial commit 2019-05-29 13:08:20 +02:00
8 changed files with 130 additions and 94 deletions

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
dist: xenial # required for Python >= 3.7
language: python
python:
- "3.7"
install:
- pip install -r requirements.txt
script:
- pytest

View File

@@ -1,35 +1,35 @@
# GOG Galaxy Integrations Python API
This Python library allows to easily build community integrations for various gaming platforms with GOG Galaxy 2.0.
This Python library allows developers to easily build community integrations for various gaming platforms with GOG Galaxy 2.0.
- refer to our <a href='https://galaxy-integrations-python-api.readthedocs.io'>documentation</a>
## Features
Each integration in GOG Galaxy 2.0 comes as a separate Python script, and is launched as a separate process, that which needs to communicate with main instance of GOG Galaxy 2.0.
Each integration in GOG Galaxy 2.0 comes as a separate Python script and is launched as a separate process that needs to communicate with the main instance of GOG Galaxy 2.0.
The provided features are:
- multistep authorisation using a browser built into GOG Galaxy 2.0
- multistep authorization using a browser built into GOG Galaxy 2.0
- support for GOG Galaxy 2.0 features:
- importing owned and detecting installed games
- installing and launching games
- importing achievements and game time
- importing friends lists and statuses
- importing friends recomendations list
- receiving and sending chat messages
- importing owned and detecting installed games
- installing and launching games
- importing achievements and game time
- importing friends lists and statuses
- importing friends recommendations list
- receiving and sending chat messages
- cache storage
## Platform Id's
Each integration can implement only one platform. Each integration must declare which platform it's integrating.
[List of possible Platofrm IDs](PLATFORM_IDs.md)
[List of possible Platform IDs](PLATFORM_IDs.md)
## Basic usage
Eeach integration should inherit from the :class:`~galaxy.api.plugin.Plugin` class. Supported methods like :meth:`~galaxy.api.plugin.Plugin.get_owned_games` should be overwritten - they are called from the GOG Galaxy client in the appropriate times.
Each of those method can raise exceptions inherited from the :exc:`~galaxy.api.jsonrpc.ApplicationError`.
Each integration should inherit from the :class:`~galaxy.api.plugin.Plugin` class. Supported methods like :meth:`~galaxy.api.plugin.Plugin.get_owned_games` should be overwritten - they are called from the GOG Galaxy client at the appropriate times.
Each of those methods can raise exceptions inherited from the :exc:`~galaxy.api.jsonrpc.ApplicationError`.
Communication between an integration and the client is also possible with the use of notifications, for example: :meth:`~galaxy.api.plugin.Plugin.update_local_game_status`.
```python
@@ -61,11 +61,13 @@ if __name__ == "__main__":
## Deployment
The client has a built-in Python 3.7 interpreter, so the integrations are delivered as python modules.
In order to be found by GOG Galaxy 2.0 an integration folder should be placed in [lookup directory](#deploy-location). Beside all the python files, the integration folder has to contain [manifest.json](#deploy-manifest) and all third-party dependencies. See an [examplary structure](#deploy-structure-example).
The client has a built-in Python 3.7 interpreter, so integrations are delivered as Python modules.
In order to be found by GOG Galaxy 2.0 an integration folder should be placed in [lookup directory](#deploy-location). Beside all the Python files, the integration folder must contain [manifest.json](#deploy-manifest) and all third-party dependencies. See an [exemplary structure](#deploy-structure-example).
### Lookup directory
<a name="deploy-location"></a>
- Windows:
`%localappdata%\GOG.com\Galaxy\plugins\installed`
@@ -75,8 +77,9 @@ In order to be found by GOG Galaxy 2.0 an integration folder should be placed in
`~/Library/Application Support/GOG.com/Galaxy/plugins/installed`
### Manifest
<a name="deploy-manifest"></a>
Obligatory JSON file to be placed in a integration folder.
<a name="deploy-manifest"></a>
Obligatory JSON file to be placed in an integration folder.
```json
{
@@ -91,6 +94,7 @@ Obligatory JSON file to be placed in a integration folder.
"script": "plugin.py"
}
```
| property | description |
|---------------|---|
| `guid` | |
@@ -99,13 +103,15 @@ Obligatory JSON file to be placed in a integration folder.
| `script` | path of the entry point module, relative to the integration folder |
### Dependencies
All third-party packages (packages not included in Python 3.7 standard library) should be deployed along with plugin files. Use the folowing command structure:
All third-party packages (packages not included in the Python 3.7 standard library) should be deployed along with plugin files. Use the following command structure:
```pip install DEP --target DIR --implementation cp --python-version 37```
For example plugin that uses *requests* has structure as follows:
For example, a plugin that uses *requests* could have the following structure:
<a name="deploy-structure-example"></a>
```bash
installed
└── my_integration

View File

@@ -1,14 +0,0 @@
stage('Upload to github')
{
node('ActiveClientMacosxBuilder') {
deleteDir()
checkout scm
withPythonEnv('/usr/local/bin/python3.7') {
withCredentials([string(credentialsId: 'github_goggalaxy', variable: 'GITHUB_TOKEN')]) {
sh 'pip install -r jenkins/requirements.txt'
def version = sh(returnStdout: true, script: 'python setup.py --version').trim()
sh "python jenkins/release.py $version"
}
}
}
}

View File

@@ -1,26 +0,0 @@
import os
import sys
from galaxy.github.exporter import transfer_repo
GITHUB_USERNAME = "goggalaxy"
GITHUB_EMAIL = "galaxy-sdk@gog.com"
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
GITHUB_REPO_NAME = "galaxy-integrations-python-api"
SOURCE_BRANCH = os.environ["GIT_REFSPEC"]
GITLAB_USERNAME = "galaxy-client"
GITLAB_REPO_NAME = "galaxy-plugin-api"
def version_provider(_):
return sys.argv[1]
gh_version = transfer_repo(
version_provider=version_provider,
source_repo_spec="git@gitlab.gog.com:{}/{}.git".format(GITLAB_USERNAME, GITLAB_REPO_NAME),
source_include_elements=["src", "docs", "tests", "requirements.txt", ".readthedocs.yml" ".gitignore", "*.md", "pytest.ini", "setup.py"],
source_branch=SOURCE_BRANCH,
dest_repo_spec="https://{}:{}@github.com/{}/{}.git".format(GITHUB_USERNAME, GITHUB_TOKEN, "gogcom", GITHUB_REPO_NAME),
dest_branch="master",
dest_user_email=GITHUB_EMAIL,
dest_user_name="GOG Galaxy SDK Team"
)

View File

@@ -1 +0,0 @@
git+ssh://git@gitlab.gog.com/galaxy-client/github-exporter.git@v0.1

View File

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

View File

@@ -1,5 +1,6 @@
import asyncio
import ssl
from contextlib import contextmanager
from http import HTTPStatus
import aiohttp
@@ -12,44 +13,69 @@ from galaxy.api.errors import (
)
DEFAULT_LIMIT = 20
DEFAULT_TIMEOUT = 60 # seconds
class HttpClient:
def __init__(self, limit=20, timeout=aiohttp.ClientTimeout(total=60), cookie_jar=None):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_verify_locations(certifi.where())
connector = aiohttp.TCPConnector(limit=limit, ssl=ssl_context)
self._session = aiohttp.ClientSession(connector=connector, timeout=timeout, cookie_jar=cookie_jar)
"""Deprecated"""
def __init__(self, limit=DEFAULT_LIMIT, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), cookie_jar=None):
connector = create_tcp_connector(limit=limit)
self._session = create_client_session(connector=connector, timeout=timeout, cookie_jar=cookie_jar)
async def close(self):
await self._session.close()
async def request(self, method, url, *args, **kwargs):
try:
response = await self._session.request(method, url, *args, **kwargs)
except asyncio.TimeoutError:
raise BackendTimeout()
except aiohttp.ServerDisconnectedError:
raise BackendNotAvailable()
except aiohttp.ClientConnectionError:
raise NetworkError()
except aiohttp.ContentTypeError:
raise UnknownBackendResponse()
except aiohttp.ClientError:
logging.exception(
"Caught exception while running {} request for {}".format(method, url))
raise UnknownError()
if response.status == HTTPStatus.UNAUTHORIZED:
raise AuthenticationRequired()
if response.status == HTTPStatus.FORBIDDEN:
raise AccessDenied()
if response.status == HTTPStatus.SERVICE_UNAVAILABLE:
raise BackendNotAvailable()
if response.status == HTTPStatus.TOO_MANY_REQUESTS:
raise TooManyRequests()
if response.status >= 500:
raise BackendError()
if response.status >= 400:
logging.warning(
"Got status {} while running {} request for {}".format(response.status, method, url))
raise UnknownError()
with handle_exception():
return await self._session.request(method, url, *args, **kwargs)
def create_tcp_connector(*args, **kwargs):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_verify_locations(certifi.where())
kwargs.setdefault("ssl", ssl_context)
kwargs.setdefault("limit", DEFAULT_LIMIT)
return aiohttp.TCPConnector(*args, **kwargs)
def create_client_session(*args, **kwargs):
kwargs.setdefault("connector", create_tcp_connector())
kwargs.setdefault("timeout", aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT))
kwargs.setdefault("raise_for_status", True)
return aiohttp.ClientSession(*args, **kwargs)
@contextmanager
def handle_exception():
try:
yield
except asyncio.TimeoutError:
raise BackendTimeout()
except aiohttp.ServerDisconnectedError:
raise BackendNotAvailable()
except aiohttp.ClientConnectionError:
raise NetworkError()
except aiohttp.ContentTypeError:
raise UnknownBackendResponse()
except aiohttp.ClientResponseError as error:
if error.status == HTTPStatus.UNAUTHORIZED:
raise AuthenticationRequired()
if error.status == HTTPStatus.FORBIDDEN:
raise AccessDenied()
if error.status == HTTPStatus.SERVICE_UNAVAILABLE:
raise BackendNotAvailable()
if error.status == HTTPStatus.TOO_MANY_REQUESTS:
raise TooManyRequests()
if error.status >= 500:
raise BackendError()
if error.status >= 400:
logging.warning(
"Got status %d while performing %s request for %s",
error.status, error.request_info.method, str(error.request_info.url)
)
raise UnknownError()
except aiohttp.ClientError:
logging.exception("Caught exception while performing request")
raise UnknownError()
return response

37
tests/test_http.py Normal file
View File

@@ -0,0 +1,37 @@
import asyncio
from http import HTTPStatus
import aiohttp
import pytest
from galaxy.api.errors import (
AccessDenied, AuthenticationRequired, BackendTimeout, BackendNotAvailable, BackendError, NetworkError,
TooManyRequests, UnknownBackendResponse, UnknownError
)
from galaxy.http import handle_exception
request_info = aiohttp.RequestInfo("http://o.pl", "GET", {})
@pytest.mark.parametrize(
"aiohttp_exception,expected_exception_type",
[
(asyncio.TimeoutError(), BackendTimeout),
(aiohttp.ServerDisconnectedError(), BackendNotAvailable),
(aiohttp.ClientConnectionError(), NetworkError),
(aiohttp.ContentTypeError(request_info, []), UnknownBackendResponse),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.UNAUTHORIZED), AuthenticationRequired),
(aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.FORBIDDEN), AccessDenied),
(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.INTERNAL_SERVER_ERROR), 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.NOT_FOUND), UnknownError),
(aiohttp.ClientError(), UnknownError)
]
)
def test_handle_exception(aiohttp_exception, expected_exception_type):
with pytest.raises(expected_exception_type):
with handle_exception():
raise aiohttp_exception