diff --git a/README.md b/README.md index a11715de1..3d09ce0e8 100644 --- a/README.md +++ b/README.md @@ -711,7 +711,9 @@ Usage: lean cloud object-store set [OPTIONS] KEY PATH Sets the data to the given key in the organization's cloud object store. - :param key: The key to set the data to. :param path: Path to the file containing the object data. + :param key: The key to set the data to. The cloud stores a key that does not follow the object store key format under + a sanitized name, with a warning naming the key to use from then on. :param path: Path to the file containing the + object data. Options: --verbose Enable debug logging diff --git a/lean/commands/cloud/object_store/set.py b/lean/commands/cloud/object_store/set.py index 5794af051..237fec67b 100644 --- a/lean/commands/cloud/object_store/set.py +++ b/lean/commands/cloud/object_store/set.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from html import unescape from click import command, argument from pathlib import Path from lean.click import LeanCommand, PathParameter @@ -23,7 +24,7 @@ def set(key: str, path: Path) -> None: """Sets the data to the given key in the organization's cloud object store. - :param key: The key to set the data to. + :param key: The key to set the data to. The cloud stores a key that does not follow the object store key format under a sanitized name, with a warning naming the key to use from then on. :param path: Path to the file containing the object data. """ organization_id = container.organization_manager.try_get_working_organization_id() @@ -31,4 +32,7 @@ def set(key: str, path: Path) -> None: api_client = container.api_client with open(path, "rb") as file: bytes_data: bytes = file.read() - api_client.object_store.set(key, bytes_data, organization_id) + response = api_client.object_store.set(key, bytes_data, organization_id) + # the cloud encodes the key in its messages for web pages + for warning in response.get("warnings", []) if isinstance(response, dict) else []: + container.logger.warn(unescape(warning)) diff --git a/lean/components/api/object_store_client.py b/lean/components/api/object_store_client.py index 76cd6bb5f..e82f5fa53 100644 --- a/lean/components/api/object_store_client.py +++ b/lean/components/api/object_store_client.py @@ -80,11 +80,12 @@ def get(self, keys: [str], organization_id: str, logger: Logger) -> str: return data["url"] - def set(self, key: str, object_data: bytes, organization_id: str) -> None: + def set(self, key: str, object_data: bytes, organization_id: str) -> dict: """Sets the given key in the Object Store. :param key: key of the object to set :param object_data: the data to set :param organization_id: the id of the organization who's object store to set data in + :return: the api response, with the key the object was stored under and any warnings about it """ payload = { "organizationId": organization_id, @@ -93,7 +94,7 @@ def set(self, key: str, object_data: bytes, organization_id: str) -> None: extra_options = { "files": {"objectData": object_data} } - self._api.post("object/set", payload, False, extra_options) + return self._api.post("object/set", payload, False, extra_options) def list(self, path: str, organization_id: str) -> str: """List all values for the given root key in the Object Store. diff --git a/tests/commands/cloud/object_store/test_set.py b/tests/commands/cloud/object_store/test_set.py index f282c14bc..72b884789 100644 --- a/tests/commands/cloud/object_store/test_set.py +++ b/tests/commands/cloud/object_store/test_set.py @@ -35,3 +35,41 @@ def test_set_sets_value_when_path_is_given() -> None: with open(file_path, "rb") as file: bytes_data: bytes = file.read() container.api_client.object_store.set.assert_called_once_with('test-key', bytes_data, 'abc') + + +def test_set_prints_the_warnings_the_api_returns() -> None: + create_fake_lean_cli_project("test", "python") + api_client = mock.Mock() + api_client.is_authenticated.return_value = True + api_client.object_store.set.return_value = { + "success": True, + "key": "trade log_ AI _ Cloud.csv", + "warnings": ["Key 'trade log: AI & Cloud.csv' is not supported: unsupported characters ':', '&'. " + "Using 'trade log_ AI _ Cloud.csv' instead"] + } + initialize_container(api_client_to_use=api_client) + file_path = str(Path.joinpath(Path.cwd(), "lean.json")) + + result = CliRunner().invoke(lean, ["cloud", "object-store", "set", "trade log: AI & Cloud.csv", file_path]) + + assert result.exit_code == 0 + output = " ".join(result.output.split()) + assert "Key 'trade log: AI & Cloud.csv' is not supported: unsupported characters ':', '&'. " \ + "Using 'trade log_ AI _ Cloud.csv' instead" in output + with open(file_path, "rb") as file: + container.api_client.object_store.set.assert_called_once_with('trade log: AI & Cloud.csv', file.read(), 'abc') + + +def test_set_prints_no_warning_when_the_api_returns_none() -> None: + create_fake_lean_cli_project("test", "python") + api_client = mock.Mock() + api_client.is_authenticated.return_value = True + api_client.object_store.set.return_value = {"success": True, "key": "folder/trade_log-2024.csv", "warnings": []} + initialize_container(api_client_to_use=api_client) + file_path = str(Path.joinpath(Path.cwd(), "lean.json")) + + result = CliRunner().invoke(lean, ["cloud", "object-store", "set", "folder/trade_log-2024.csv", file_path]) + + assert result.exit_code == 0 + assert "is not supported" not in result.output + container.api_client.object_store.set.assert_called_once_with('folder/trade_log-2024.csv', mock.ANY, 'abc')