From 88ee1a89af4258530232e1e27e44f9f6f5b5b23d Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 21:03:46 +0200 Subject: [PATCH 1/6] Hide testing files (for now) --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6dcb2a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/.*.py \ No newline at end of file From 1a07b6613aa8d288ea5229db203a98b201da05d4 Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 22:45:25 +0200 Subject: [PATCH 2/6] Extend __requests to allow multipart data, implement save uploading --- .gitignore | 4 ++- pyfactorybridge/__init__.py | 70 ++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index e6dcb2a..14107da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -**/.*.py \ No newline at end of file +**/.*.py +pyfactorybridge/testing.py +.vscode \ No newline at end of file diff --git a/pyfactorybridge/__init__.py b/pyfactorybridge/__init__.py index b653d4d..dc72546 100644 --- a/pyfactorybridge/__init__.py +++ b/pyfactorybridge/__init__.py @@ -1,5 +1,7 @@ import requests import logging +from json import dumps +from pathlib import Path from pyfactorybridge.exceptions import ServerExceptions, ServerError from pyfactorybridge.authentication import BearerAuth @@ -34,19 +36,43 @@ def __init__(self, address, password=None, token=None): else: logging.error("No password provided, some functions may not work.") - def __request(self, function, properties={}) -> dict[Any]: + def __build_request_data(self, function: str, properties: dict | None = None) -> None: request_data = {"data": {"clientCustomData": ""}, "function": function} - - for property_name, property_value in properties.items(): - request_data["data"][property_name] = property_value + if isinstance(properties, dict): + for property_name, property_value in properties.items(): + request_data["data"][property_name] = property_value + return request_data + + def __request(self, function: str, properties: dict | None = None, multiparts: dict | None = None) -> dict[Any]: + request_data = self.__build_request_data(function, properties) + + http_method_kwargs = {} + if multiparts is None: + # Simple application/json request + http_method_kwargs = { + "json": request_data, + } + else: + # multipart/form-data request + if "data" not in multiparts: + multiparts["data"] = ( + # Multipart file name + None, + # Multipart content (byte-like) + dumps(request_data), + # Content-Type + "application/json", + ) + http_method_kwargs = { + "files": multiparts, + } try: response_data = requests.post( self.URL, verify=False, - json=request_data, - auth=self.auth if self.auth else None, - headers={"Content-Type": "application/json"}, + auth=self.auth or None, + **http_method_kwargs ) if response_data.text: @@ -167,8 +193,34 @@ def load_game(self, SaveName, EnableAdvancedGameSettings) -> dict[str, str]: }, ) - def upload_save_game(self, data) -> None: - raise NotImplementedError + def upload_save_game( + self, + save_file_path: str, + SaveName: str | None = None, + LoadSaveGame: bool = False, + EnableAdvancedGameSettings: bool = False, + ) -> dict[str, str]: + with open(save_file_path, "rb") as save_file_handle: + save_file_path_obj = Path(save_file_path) + multiparts = { + "saveGameFile": ( + # Save file basename (including .sav) + save_file_path_obj.name, + # Save file content + save_file_handle.read(), + # Content-Type + "application/octet-stream", + ) + } + return self.__request( + function="UploadSaveGame", + properties={ + "SaveName": SaveName or save_file_path_obj.stem, # Save file name without file extension + "LoadSaveGame": LoadSaveGame, + "EnableAdvancedGameSettings": EnableAdvancedGameSettings, + }, + multiparts=multiparts, + ) def download_save_game(self, SaveName) -> None: raise NotImplementedError From b1c92ddd375888f9654fdb0c3cfd18ff971d2964 Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 23:04:16 +0200 Subject: [PATCH 3/6] Small fix: "or None" seems unnecessary --- pyfactorybridge/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfactorybridge/__init__.py b/pyfactorybridge/__init__.py index dc72546..9591fd1 100644 --- a/pyfactorybridge/__init__.py +++ b/pyfactorybridge/__init__.py @@ -71,7 +71,7 @@ def __request(self, function: str, properties: dict | None = None, multiparts: d response_data = requests.post( self.URL, verify=False, - auth=self.auth or None, + auth=self.auth, **http_method_kwargs ) From 25998a0ec3f3337179422f514ea1f36b901140b2 Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 23:07:56 +0200 Subject: [PATCH 4/6] Remove .gitignore again --- .gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 14107da..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -**/.*.py -pyfactorybridge/testing.py -.vscode \ No newline at end of file From 98456b41b14ded4043ba8c612c57c917caab9ac5 Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 23:10:46 +0200 Subject: [PATCH 5/6] Run ruff to format __init__.py --- pyfactorybridge/__init__.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/pyfactorybridge/__init__.py b/pyfactorybridge/__init__.py index 9591fd1..386597a 100644 --- a/pyfactorybridge/__init__.py +++ b/pyfactorybridge/__init__.py @@ -36,14 +36,21 @@ def __init__(self, address, password=None, token=None): else: logging.error("No password provided, some functions may not work.") - def __build_request_data(self, function: str, properties: dict | None = None) -> None: + def __build_request_data( + self, function: str, properties: dict | None = None + ) -> None: request_data = {"data": {"clientCustomData": ""}, "function": function} if isinstance(properties, dict): for property_name, property_value in properties.items(): request_data["data"][property_name] = property_value return request_data - def __request(self, function: str, properties: dict | None = None, multiparts: dict | None = None) -> dict[Any]: + def __request( + self, + function: str, + properties: dict | None = None, + multiparts: dict | None = None, + ) -> dict[Any]: request_data = self.__build_request_data(function, properties) http_method_kwargs = {} @@ -69,10 +76,7 @@ def __request(self, function: str, properties: dict | None = None, multiparts: d try: response_data = requests.post( - self.URL, - verify=False, - auth=self.auth, - **http_method_kwargs + self.URL, verify=False, auth=self.auth, **http_method_kwargs ) if response_data.text: @@ -194,12 +198,12 @@ def load_game(self, SaveName, EnableAdvancedGameSettings) -> dict[str, str]: ) def upload_save_game( - self, - save_file_path: str, - SaveName: str | None = None, - LoadSaveGame: bool = False, - EnableAdvancedGameSettings: bool = False, - ) -> dict[str, str]: + self, + save_file_path: str, + SaveName: str | None = None, + LoadSaveGame: bool = False, + EnableAdvancedGameSettings: bool = False, + ) -> dict[str, str]: with open(save_file_path, "rb") as save_file_handle: save_file_path_obj = Path(save_file_path) multiparts = { @@ -215,7 +219,8 @@ def upload_save_game( return self.__request( function="UploadSaveGame", properties={ - "SaveName": SaveName or save_file_path_obj.stem, # Save file name without file extension + "SaveName": SaveName + or save_file_path_obj.stem, # Save file name without file extension "LoadSaveGame": LoadSaveGame, "EnableAdvancedGameSettings": EnableAdvancedGameSettings, }, From 1a3110947c66c7bc2c34c6e1406ccbba45616d30 Mon Sep 17 00:00:00 2001 From: Jan Erik Karuc Date: Mon, 16 Sep 2024 23:25:54 +0200 Subject: [PATCH 6/6] Change wrong return type of None of __build_request_data() to dict --- pyfactorybridge/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfactorybridge/__init__.py b/pyfactorybridge/__init__.py index 386597a..7fcc594 100644 --- a/pyfactorybridge/__init__.py +++ b/pyfactorybridge/__init__.py @@ -38,7 +38,7 @@ def __init__(self, address, password=None, token=None): def __build_request_data( self, function: str, properties: dict | None = None - ) -> None: + ) -> dict: request_data = {"data": {"clientCustomData": ""}, "function": function} if isinstance(properties, dict): for property_name, property_value in properties.items():