diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd185104..758a023fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ You can check your current version with the following command: ``` For more information, see [UP42 Python package description](https://pypi.org/project/up42-py/). +## 1.0.4a2 + +**May 24, 2024** + +- Added workspace singleton in `main.py`, encapsulating global state (auth, workspace id). +- Inject auth and workspace id instead of passing a containing object. + ## 1.0.4a1 diff --git a/pyproject.toml b/pyproject.toml index 7dfe505ad..203eb89cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "up42-py" -version = "1.0.4a1" +version = "1.0.4a2" description = "Python SDK for UP42, the geospatial marketplace and developer platform." authors = ["UP42 GmbH "] license = "https://github.com/up42/up42-py/blob/master/LICENSE" diff --git a/tests/fixtures/fixtures_auth.py b/tests/fixtures/fixtures_auth.py index fda1ea1f1..19ffbabc5 100644 --- a/tests/fixtures/fixtures_auth.py +++ b/tests/fixtures/fixtures_auth.py @@ -8,20 +8,10 @@ @pytest.fixture def auth_mock(requests_mock: req_mock.Mocker) -> up42_auth.Auth: - json_get_token = { + token_payload = { "data": {"accessToken": constants.TOKEN}, "access_token": constants.TOKEN, "token_type": "bearer", } - requests_mock.post("https://api.up42.com/oauth/token", json=json_get_token) - requests_mock.get( - url="https://api.up42.com/users/me", - json={"data": {"id": constants.WORKSPACE_ID}}, - ) - # get_credits_balance - url_get_credits_balance = f"{constants.API_HOST}/accounts/me/credits/balance" - requests_mock.get( - url=url_get_credits_balance, - json=constants.JSON_BALANCE, - ) + requests_mock.post("https://api.up42.com/oauth/token", json=token_payload) return up42_auth.Auth(username=constants.USER_EMAIL, password=constants.PASSWORD) diff --git a/tests/fixtures/fixtures_catalog.py b/tests/fixtures/fixtures_catalog.py index 0e0c023ae..8e1e24fdb 100644 --- a/tests/fixtures/fixtures_catalog.py +++ b/tests/fixtures/fixtures_catalog.py @@ -42,7 +42,7 @@ def catalog_mock(auth_mock, requests_mock): json_data_product_schema = json.load(json_file) requests_mock.get(url=url_data_product_schema, json=json_data_product_schema) - return catalog.Catalog(auth=auth_mock) + return catalog.Catalog(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) @pytest.fixture() @@ -77,7 +77,7 @@ def catalog_pagination_mock(auth_mock, requests_mock): [{"json": search_response_json}, {"json": pagination_response_json}], ) - return catalog.Catalog(auth=auth_mock) + return catalog.Catalog(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) @pytest.fixture() @@ -119,4 +119,4 @@ def catalog_usagetype_mock(auth_mock, requests_mock): ], ) - return catalog.Catalog(auth=auth_mock) + return catalog.Catalog(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) diff --git a/tests/fixtures/fixtures_globals.py b/tests/fixtures/fixtures_globals.py index ea9062bef..aba9f90aa 100644 --- a/tests/fixtures/fixtures_globals.py +++ b/tests/fixtures/fixtures_globals.py @@ -269,5 +269,3 @@ "updatedAt": "2022-06-20T04:05:31.755744Z", } } - -JSON_BALANCE = {"data": {"balance": 10693}} diff --git a/tests/fixtures/fixtures_storage.py b/tests/fixtures/fixtures_storage.py index 2d06632e8..1307d5e08 100644 --- a/tests/fixtures/fixtures_storage.py +++ b/tests/fixtures/fixtures_storage.py @@ -39,4 +39,4 @@ def storage_mock(auth_mock, requests_mock): # orders info url_order_info = f"{constants.API_HOST}/v2/orders/{constants.ORDER_ID}" requests_mock.get(url=url_order_info, json=constants.JSON_ORDER) - return storage.Storage(auth=auth_mock) + return storage.Storage(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) diff --git a/tests/fixtures/fixtures_tasking.py b/tests/fixtures/fixtures_tasking.py index 049b6dc45..30abcf99a 100644 --- a/tests/fixtures/fixtures_tasking.py +++ b/tests/fixtures/fixtures_tasking.py @@ -109,7 +109,7 @@ def tasking_mock(auth_mock, requests_mock): json=accepted_id_response_json, ) - return tasking.Tasking(auth=auth_mock) + return tasking.Tasking(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) @pytest.fixture() @@ -148,7 +148,7 @@ def tasking_get_feasibility_mock(auth_mock, requests_mock): json_data = json.load(json_file) requests_mock.get(url=get_feasibility_decision_param, json=json_data) - return tasking.Tasking(auth=auth_mock) + return tasking.Tasking(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) @pytest.fixture() @@ -168,4 +168,4 @@ def tasking_choose_feasibility_mock(auth_mock, requests_mock): "detail": {}, } requests_mock.patch(url=choose_feasibility_url, status_code=405, json=response) - return tasking.Tasking(auth=auth_mock) + return tasking.Tasking(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) diff --git a/tests/fixtures/fixtures_webhook.py b/tests/fixtures/fixtures_webhook.py index 726cd9cbe..ff717152c 100644 --- a/tests/fixtures/fixtures_webhook.py +++ b/tests/fixtures/fixtures_webhook.py @@ -8,11 +8,11 @@ @pytest.fixture def webhook_mock(auth_mock, requests_mock): # webhook info - url_webhook_info = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks/{constants.WEBHOOK_ID}" + url_webhook_info = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks/{constants.WEBHOOK_ID}" requests_mock.get(url=url_webhook_info, json=constants.JSON_WEBHOOK) # test event - url_test_event = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks/{constants.WEBHOOK_ID}/tests" + url_test_event = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks/{constants.WEBHOOK_ID}/tests" json_test_event = { "data": { "startedAt": "2022-06-20T04:33:48.770826Z", @@ -23,14 +23,18 @@ def webhook_mock(auth_mock, requests_mock): requests_mock.post(url=url_test_event, json=json_test_event) # update - url_update = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks/{constants.WEBHOOK_ID}" + url_update = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks/{constants.WEBHOOK_ID}" requests_mock.put(url=url_update, json=constants.JSON_WEBHOOK) # delete - url_delete = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks/{constants.WEBHOOK_ID}" + url_delete = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks/{constants.WEBHOOK_ID}" requests_mock.delete(url=url_delete) - return webhooks.Webhook(auth=auth_mock, webhook_id=constants.WEBHOOK_ID) + return webhooks.Webhook( + auth=auth_mock, + workspace_id=constants.WORKSPACE_ID, + webhook_id=constants.WEBHOOK_ID, + ) @pytest.fixture @@ -41,7 +45,7 @@ def webhooks_mock(auth_mock, requests_mock): requests_mock.get(url=url_events, json=events_json) # get webhooks - url_webhooks = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks" + url_webhooks = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks" webhooks_json = { "data": [ { @@ -70,7 +74,7 @@ def webhooks_mock(auth_mock, requests_mock): requests_mock.get(url=url_webhooks, json=webhooks_json) # create webhook - url_create_webhook = f"{constants.API_HOST}/workspaces/{auth_mock.workspace_id}/webhooks" + url_create_webhook = f"{constants.API_HOST}/workspaces/{constants.WORKSPACE_ID}/webhooks" requests_mock.post(url=url_create_webhook, json=constants.JSON_WEBHOOK) - return webhooks.Webhooks(auth=auth_mock) + return webhooks.Webhooks(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) diff --git a/tests/test_auth.py b/tests/test_auth.py index de5d9cb5d..36ef53b88 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -13,7 +13,6 @@ CONFIG_FILE = "some-config-file" TOKEN_ENDPOINT = constants.API_HOST + "/oauth/token" -WORKSPACE_ENDPOINT = constants.API_HOST + "/users/me" URL = constants.API_HOST + "/some-url" RESPONSE_TEXT = "some-response-text" ERROR = {"some": "error"} @@ -67,37 +66,29 @@ def test_should_collect_credentials(self): client.session = session -def create_auth(requests_mock: req_mock.Mocker): - credential_sources = [{"some": "credentials"}] - get_sources = mock.MagicMock(return_value=credential_sources) - create_client = mock.MagicMock(return_value=client) - - requests_mock.get( - WORKSPACE_ENDPOINT, - json={"data": {"id": constants.WORKSPACE_ID}}, - ) - auth = up42_auth.Auth( - cfg_file=CONFIG_FILE, - username=constants.USER_EMAIL, - password=constants.PASSWORD, - get_credential_sources=get_sources, - create_client=create_client, - ) - - get_sources.assert_called_once_with( - CONFIG_FILE, - constants.USER_EMAIL, - constants.PASSWORD, - ) - create_client.assert_called_once_with(credential_sources, TOKEN_ENDPOINT) - return auth +class TestAuth: + def setup_method(self, _): + credential_sources = [{"some": "credentials"}] + get_sources = mock.MagicMock(return_value=credential_sources) + create_client = mock.MagicMock(return_value=client) + + self.auth = up42_auth.Auth( + cfg_file=CONFIG_FILE, + username=constants.USER_EMAIL, + password=constants.PASSWORD, + get_credential_sources=get_sources, + create_client=create_client, + ) + get_sources.assert_called_once_with( + CONFIG_FILE, + constants.USER_EMAIL, + constants.PASSWORD, + ) + create_client.assert_called_once_with(credential_sources, TOKEN_ENDPOINT) -class TestAuth: - def test_should_authenticate_when_created(self, requests_mock: req_mock.Mocker): - auth = create_auth(requests_mock) - assert auth.workspace_id == constants.WORKSPACE_ID - assert auth.session == session + def test_should_authenticate_when_created(self): + assert self.auth.session == session @pytest.mark.parametrize( "expected", @@ -114,7 +105,6 @@ def test_should_pass_dict_for_json_response( expected: dict, requests_mock: req_mock.Mocker, ): - auth = create_auth(requests_mock) requests_mock.request( http_method, URL, @@ -122,12 +112,11 @@ def test_should_pass_dict_for_json_response( json=expected, additional_matcher=match_request_body(request_data), ) - assert auth.request(http_method, URL, request_data) == expected + assert self.auth.request(http_method, URL, request_data) == expected def test_should_pass_text_for_text_response( self, http_method: str, request_data: dict, requests_mock: req_mock.Mocker ): - auth = create_auth(requests_mock) requests_mock.request( http_method, URL, @@ -135,10 +124,9 @@ def test_should_pass_text_for_text_response( text=RESPONSE_TEXT, additional_matcher=match_request_body(request_data), ) - assert auth.request(http_method, URL, request_data) == RESPONSE_TEXT + assert self.auth.request(http_method, URL, request_data) == RESPONSE_TEXT def test_should_pass_response(self, http_method: str, request_data: dict, requests_mock: req_mock.Mocker): - auth = create_auth(requests_mock) requests_mock.request( http_method, URL, @@ -146,13 +134,11 @@ def test_should_pass_response(self, http_method: str, request_data: dict, reques text=RESPONSE_TEXT, additional_matcher=match_request_body(request_data), ) - response = auth.request(http_method, URL, request_data, return_text=False) + response = self.auth.request(http_method, URL, request_data, return_text=False) assert isinstance(response, requests.Response) assert response.text == RESPONSE_TEXT def test_fails_if_v1_api_request_fails(self, http_method: str, request_data: dict, requests_mock: req_mock.Mocker): - auth = create_auth(requests_mock) - requests_mock.request( http_method, URL, @@ -161,7 +147,7 @@ def test_fails_if_v1_api_request_fails(self, http_method: str, request_data: dic additional_matcher=match_request_body(request_data), ) with pytest.raises(ValueError) as exc_info: - auth.request(http_method, URL, request_data) + self.auth.request(http_method, URL, request_data) assert str(exc_info.value) == str(ERROR) @@ -175,7 +161,6 @@ def test_fails_if_status_code_is_bad( error: Optional[Dict], requests_mock: req_mock.Mocker, ): - auth = create_auth(requests_mock) requests_mock.request( http_method, URL, @@ -185,5 +170,5 @@ def test_fails_if_status_code_is_bad( additional_matcher=match_request_body(request_data), ) with pytest.raises(requests.HTTPError) as exc_info: - auth.request(http_method, URL, request_data, return_text=return_text) + self.auth.request(http_method, URL, request_data, return_text=return_text) assert str(exc_info.value) == (json.dumps(error) if error else "") diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 866ea44f7..65d383cb3 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -261,9 +261,8 @@ def test_construct_order_parameters(catalog_mock): assert order_parameters["params"]["acquisitionMode"] is None -# pylint: disable=unused-argument def test_estimate_order_from_catalog(catalog_order_parameters, requests_mock, auth_mock): - catalog_instance = catalog.Catalog(auth=auth_mock) + catalog_instance = catalog.Catalog(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) expected_payload = { "summary": {"totalCredits": 100, "totalSize": 0.1, "unit": "SQ_KM"}, "results": [{"index": 0, "credits": 100, "unit": "SQ_KM", "size": 0.1}], @@ -278,7 +277,7 @@ def test_estimate_order_from_catalog(catalog_order_parameters, requests_mock, au def test_order_from_catalog( order_parameters, - order_mock, + order_mock, # pylint: disable=unused-argument catalog_mock, requests_mock, ): diff --git a/tests/test_initialization.py b/tests/test_initialization.py index 5345da5fb..6b328e5a4 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -1,50 +1,43 @@ +from unittest import mock + import pytest import up42 -from up42 import catalog, main, tasking +from up42 import catalog, main, storage, tasking from .fixtures import fixtures_globals as constants -def test_initialize_object_without_auth_raises(): - main._auth = None # pylint: disable=protected-access - - with pytest.raises(RuntimeError): +def test_fails_to_initialize_if_not_authenticated(): + with pytest.raises(main.UserNotAuthenticated): up42.initialize_catalog() - with pytest.raises(RuntimeError): + with pytest.raises(main.UserNotAuthenticated): up42.initialize_storage() - with pytest.raises(RuntimeError): + with pytest.raises(main.UserNotAuthenticated): up42.initialize_order(order_id=constants.ORDER_ID) - with pytest.raises(RuntimeError): + with pytest.raises(main.UserNotAuthenticated): up42.initialize_asset(asset_id=constants.ASSET_ID) -def test_global_auth_initialize_objects( - storage_mock, +def test_should_initialize_objects( + auth_mock, order_mock, asset_mock, ): - up42.authenticate(username=constants.USER_EMAIL, password=constants.PASSWORD) - catalog_obj = up42.initialize_catalog() - assert isinstance(catalog_obj, catalog.Catalog) - storage_obj = up42.initialize_storage() - assert storage_obj.workspace_id == storage_mock.workspace_id - order_obj = up42.initialize_order(order_id=constants.ORDER_ID) - assert order_obj.info == order_mock.info - asset_obj = up42.initialize_asset(asset_id=constants.ASSET_ID) - assert asset_obj.info == asset_mock.info - - -@pytest.fixture(autouse=True) -def setup_workspace(requests_mock): - requests_mock.post("https://api.up42.com/oauth/token", json={"access_token": constants.TOKEN}) - requests_mock.get( - url="https://api.up42.com/users/me", - json={"data": {"id": constants.WORKSPACE_ID}}, - ) - - -def test_should_initialize_tasking(): - up42.authenticate(username=constants.USER_EMAIL, password=constants.PASSWORD) - result = up42.initialize_tasking() - assert isinstance(result, tasking.Tasking) + with mock.patch("up42.main.workspace") as workspace_mock: + workspace_mock.id = constants.WORKSPACE_ID + workspace_mock.auth = auth_mock + + catalog_obj = up42.initialize_catalog() + assert isinstance(catalog_obj, catalog.Catalog) + + storage_obj = up42.initialize_storage() + assert isinstance(storage_obj, storage.Storage) + assert storage_obj.workspace_id == constants.WORKSPACE_ID + + order_obj = up42.initialize_order(order_id=constants.ORDER_ID) + assert order_obj.info == order_mock.info + asset_obj = up42.initialize_asset(asset_id=constants.ASSET_ID) + assert asset_obj.info == asset_mock.info + result = up42.initialize_tasking() + assert isinstance(result, tasking.Tasking) diff --git a/tests/test_main.py b/tests/test_main.py index cc7edb3f4..41cdc39ec 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,3 +1,5 @@ +from unittest import mock + import pytest from up42 import main @@ -5,44 +7,60 @@ from .fixtures import fixtures_globals as constants -@pytest.fixture(autouse=True) -def setup_auth_mock(auth_mock): - main._auth = auth_mock # pylint: disable=protected-access - yield - - -def test_get_credits_balance(): - balance = main.get_credits_balance() - assert isinstance(balance, dict) - assert "balance" in balance +class TestWorkspace: + def test_fails_to_provide_properties_if_not_authenticated(self): + with pytest.raises(main.UserNotAuthenticated): + _ = main.workspace.auth + with pytest.raises(main.UserNotAuthenticated): + _ = main.workspace.id + def test_should_authenticate(self, requests_mock): + requests_mock.post("https://api.up42.com/oauth/token", json={"access_token": constants.TOKEN}) + requests_mock.get( + url="https://api.up42.com/users/me", + json={"data": {"id": constants.WORKSPACE_ID}}, + ) + main.workspace.authenticate(username=constants.USER_EMAIL, password=constants.PASSWORD) + assert main.workspace.id == constants.WORKSPACE_ID -def test_fails_to_get_auth_safely_if_unauthenticated(): - main._auth = None # pylint: disable=protected-access - with pytest.raises(ValueError): - main.get_auth_safely() +# TODO: these tests to be moved to test_initialization +class TestNonWorkspace: + @pytest.fixture(autouse=True) + def workspace(self, auth_mock): + with mock.patch("up42.main.workspace") as workspace_mock: + workspace_mock.auth = auth_mock + workspace_mock.id = constants.WORKSPACE_ID + yield -def test_get_webhook_events(requests_mock): - url_webhook_events = f"{constants.API_HOST}/webhooks/events" - events = ["some-event"] - requests_mock.get( - url=url_webhook_events, - json={ - "data": events, - "error": {}, - }, - ) - assert main.get_webhook_events() == events + def test_get_webhook_events(self, requests_mock): + url_webhook_events = f"{constants.API_HOST}/webhooks/events" + events = ["some-event"] + requests_mock.get( + url=url_webhook_events, + json={ + "data": events, + "error": {}, + }, + ) + assert main.get_webhook_events() == events + @pytest.mark.parametrize("return_json", [False, True]) + def test_get_webhooks(self, webhooks_mock, return_json): + webhooks = main.get_webhooks(return_json=return_json) + expected = webhooks_mock.get_webhooks(return_json=return_json) + if return_json: + assert webhooks == expected + else: + for hook, expected_hook in zip(webhooks, expected): + assert hook.webhook_id == expected_hook.webhook_id + assert hook._info == expected_hook._info # pylint: disable=protected-access -@pytest.mark.parametrize("return_json", [False, True]) -def test_get_webhooks(webhooks_mock, return_json): - webhooks = main.get_webhooks(return_json=return_json) - expected = webhooks_mock.get_webhooks(return_json=return_json) - if return_json: - assert webhooks == expected - else: - for hook, expected_hook in zip(webhooks, expected): - assert hook.webhook_id == expected_hook.webhook_id - assert hook._info == expected_hook._info # pylint: disable=protected-access + def test_get_credits_balance(self, requests_mock): + balance_url = f"{constants.API_HOST}/accounts/me/credits/balance" + balance = {"balance": 10693} + requests_mock.get( + url=balance_url, + json={"data": balance}, + ) + assert main.get_credits_balance() == balance diff --git a/tests/test_order.py b/tests/test_order.py index f004cd4c9..0c67e96a1 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -111,7 +111,7 @@ def test_place_order(catalog_order_parameters, auth_mock, order_mock, requests_m "errors": [], }, ) - order_placed = order.Order.place(auth_mock, catalog_order_parameters) + order_placed = order.Order.place(auth_mock, catalog_order_parameters, constants.WORKSPACE_ID) assert order_placed == order_mock assert order_placed.order_id == constants.ORDER_ID assert order_placed.order_parameters == catalog_order_parameters @@ -127,7 +127,7 @@ def test_place_order_fails_if_response_contains_error(catalog_order_parameters, }, ) with pytest.raises(ValueError) as err: - order.Order.place(auth_mock, catalog_order_parameters) + order.Order.place(auth_mock, catalog_order_parameters, constants.WORKSPACE_ID) assert error_content in str(err.value) diff --git a/tests/test_storage.py b/tests/test_storage.py index d1929fb17..82a835448 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -145,7 +145,7 @@ def test_get_assets_pagination(auth_mock, requests_mock): url_storage_assets_paginated = f"{constants.API_HOST}/v2/assets?sort=createdAt,asc&size=50" requests_mock.get(url=url_storage_assets_paginated, json=json_assets_paginated) - storage_results = storage.Storage(auth=auth_mock) + storage_results = storage.Storage(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) assets = storage_results.get_assets(limit=74, sortby="createdAt", descending=False) assert len(assets) == 74 assert isinstance(assets[0], asset.Asset) @@ -272,7 +272,7 @@ def test_get_orders_v2_endpoint_params(auth_mock, requests_mock, params, expecte url_params = "&".join( [ "sort=createdAt%2Cdesc", - f"workspaceId={constants.WORKSPACE_ID}" if params["workspace_orders"] else "", + (f"workspaceId={constants.WORKSPACE_ID}" if params["workspace_orders"] else ""), f"""displayName={params["name"]}""" if params["name"] else "", *[f"status={status}" for status in endpoint_statuses], "size=50", @@ -291,7 +291,7 @@ def test_get_orders_v2_endpoint_params(auth_mock, requests_mock, params, expecte ) for output in expected_results ] - storage_results = storage.Storage(auth=auth_mock) + storage_results = storage.Storage(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) orders = storage_results.get_orders(**params) assert orders == expected_results @@ -328,11 +328,11 @@ def test_get_orders_pagination(auth_mock, requests_mock): # assets pages url_storage_orders_paginated = ( - f"{constants.API_HOST}/v2/orders?sort=createdAt,asc&workspaceId={auth_mock.workspace_id}&size=50" + f"{constants.API_HOST}/v2/orders?sort=createdAt,asc&workspaceId={constants.WORKSPACE_ID}&size=50" ) requests_mock.get(url=url_storage_orders_paginated, json=json_orders_paginated) - storage_results = storage.Storage(auth=auth_mock) + storage_results = storage.Storage(auth=auth_mock, workspace_id=constants.WORKSPACE_ID) orders = storage_results.get_orders(limit=74, sortby="createdAt", descending=False) assert len(orders) == 74 assert isinstance(orders[0], order.Order) diff --git a/up42/__init__.py b/up42/__init__.py index 5f1200580..aa363107d 100644 --- a/up42/__init__.py +++ b/up42/__init__.py @@ -17,6 +17,8 @@ ``` """ +from typing import Callable, Type, Union, cast + # pylint: disable=only-importing-modules-is-allowed from up42.asset import Asset from up42.auth import Auth @@ -38,9 +40,11 @@ from up42.webhooks import Webhook __version__ = get_up42_py_version() - __all__ = [ - obj.__name__ + cast( + Union[Type, Callable], + obj, + ).__name__ for obj in [ Asset, Auth, diff --git a/up42/auth.py b/up42/auth.py index 10b36d883..874ea54ea 100644 --- a/up42/auth.py +++ b/up42/auth.py @@ -1,6 +1,7 @@ """ UP42 authentication mechanism and base requests functionality """ + import json import pathlib from typing import Callable, Dict, List, Optional, Union @@ -51,22 +52,14 @@ def __init__( username: The username for the UP42 account (email UP42 console). password: Password for the UP42 console login. """ - self.workspace_id: Optional[str] = None credential_sources = get_credential_sources(cfg_file, username, password) self.client = create_client(credential_sources, host.endpoint("/oauth/token")) - self._get_workspace() logger.info("Authentication with UP42 successful!") @property def session(self) -> requests.Session: return self.client.session - def _get_workspace(self) -> None: - """Get user id belonging to authenticated account.""" - url = host.endpoint("/users/me") - resp = self.request("GET", url) - self.workspace_id = resp["data"]["id"] - # pylint: disable=dangerous-default-value def _request_helper( self, diff --git a/up42/catalog.py b/up42/catalog.py index d9753f553..d8e58f420 100644 --- a/up42/catalog.py +++ b/up42/catalog.py @@ -22,8 +22,9 @@ class CatalogBase: The base for Catalog and Tasking class, shared functionality. """ - def __init__(self, auth: up42_auth.Auth): + def __init__(self, auth: up42_auth.Auth, workspace_id: str): self.auth = auth + self.workspace_id = workspace_id self.type: Optional[str] = None def get_data_products(self, basic: bool = True) -> Union[Dict, List[Dict]]: @@ -150,7 +151,7 @@ def place_order( warnings.warn(message, DeprecationWarning, stacklevel=2) elif order_parameters is None: raise ValueError("Please provide the 'order_parameters' parameter!") - placed_order = order.Order.place(self.auth, order_parameters) # type: ignore + placed_order = order.Order.place(self.auth, order_parameters, self.workspace_id) # type: ignore if track_status: placed_order.track_status(report_time) return placed_order @@ -170,8 +171,8 @@ class Catalog(CatalogBase): [CatalogBase](catalogbase-reference.md) class. """ - def __init__(self, auth: up42_auth.Auth): - super().__init__(auth) + def __init__(self, auth: up42_auth.Auth, workspace_id: str): + super().__init__(auth, workspace_id) self.quicklooks: Optional[List[str]] = None self.type: str = "ARCHIVE" self.data_products: Optional[Dict] = None diff --git a/up42/initialization.py b/up42/initialization.py index 11d13cd07..5fd00c8c4 100644 --- a/up42/initialization.py +++ b/up42/initialization.py @@ -8,61 +8,55 @@ INITIALIZED_MSG = "Initialized %s" -@main.check_auth def initialize_catalog() -> catalog.Catalog: """ Returns a Catalog object for using the catalog search. """ - return catalog.Catalog(auth=main.get_auth_safely()) + return catalog.Catalog(auth=main.workspace.auth, workspace_id=main.workspace.id) -@main.check_auth def initialize_tasking() -> "tasking.Tasking": """ Returns a Tasking object for creating satellite tasking orders. """ - return tasking.Tasking(auth=main.get_auth_safely()) + return tasking.Tasking(auth=main.workspace.auth, workspace_id=main.workspace.id) -@main.check_auth def initialize_storage() -> storage.Storage: """ Returns a Storage object to list orders and assets. """ - return storage.Storage(auth=main.get_auth_safely()) + return storage.Storage(auth=main.workspace.auth, workspace_id=main.workspace.id) -@main.check_auth def initialize_order(order_id: str) -> order.Order: """ Returns an Order object (has to exist on UP42). Args: order_id: The UP42 order_id """ - up42_order = order.Order(auth=main.get_auth_safely(), order_id=order_id) + up42_order = order.Order(auth=main.workspace.auth, order_id=order_id) logger.info(INITIALIZED_MSG, up42_order) return up42_order -@main.check_auth def initialize_asset(asset_id: str) -> asset.Asset: """ Returns an Asset object (has to exist on UP42). Args: asset_id: The UP42 asset_id """ - up42_asset = asset.Asset(auth=main.get_auth_safely(), asset_id=asset_id) + up42_asset = asset.Asset(auth=main.workspace.auth, asset_id=asset_id) logger.info(INITIALIZED_MSG, up42_asset) return up42_asset -@main.check_auth def initialize_webhook(webhook_id: str) -> webhooks.Webhook: """ Returns a Webhook object (has to exist on UP42). Args: webhook_id: The UP42 webhook_id """ - webhook = webhooks.Webhook(auth=main.get_auth_safely(), webhook_id=webhook_id) + webhook = webhooks.Webhook(auth=main.workspace.auth, workspace_id=main.workspace.id, webhook_id=webhook_id) logger.info(INITIALIZED_MSG, webhook) return webhook diff --git a/up42/main.py b/up42/main.py index 359cfe13b..648945931 100644 --- a/up42/main.py +++ b/up42/main.py @@ -1,8 +1,7 @@ -import functools import logging import pathlib import warnings -from typing import List, Optional, Union +from typing import Any, List, Optional, Union from up42 import auth as up42_auth from up42 import host, utils, webhooks @@ -11,54 +10,59 @@ warnings.simplefilter(action="ignore", category=FutureWarning) -_auth: Optional[up42_auth.Auth] = None +class UserNotAuthenticated(ValueError): + pass -def authenticate( - cfg_file: Optional[Union[str, pathlib.Path]] = None, - username: Optional[str] = None, - password: Optional[str] = None, -): - """ - Authenticate with UP42, either using account credentials or a config JSON file - containing the corresponding credentials. - Also see the documentation https://sdk.up42.com/authentication/ - Args: - cfg_file: File path to the cfg.json with {username: "...", password: "..."}. - username: The username for the UP42 account (email UP42 console). - password: Password for the UP42 console login. - """ - global _auth - _auth = up42_auth.Auth( - cfg_file=cfg_file, - username=username, - password=password, - ) +def _authenticated(value: Any): + if value: + return value + raise UserNotAuthenticated("User not authenticated.") -def get_auth_safely() -> up42_auth.Auth: - if _auth: - return _auth - raise ValueError("User not authenticated.") +class _Workspace: + _auth: Optional[up42_auth.Auth] = None + _id: Optional[str] = None + @property + def auth(self): + return _authenticated(self._auth) + + @property + def id(self): + return _authenticated(self._id) + + def authenticate( + self, + cfg_file: Optional[Union[str, pathlib.Path]] = None, + username: Optional[str] = None, + password: Optional[str] = None, + ): + """ + Authenticate with UP42, either using account credentials or a config JSON file + containing the corresponding credentials. + + Args: + cfg_file: File path to the cfg.json with {username: "...", password: "..."}. + username: The username for the UP42 account (email UP42 console). + password: Password for the UP42 console login. + """ + self._auth = up42_auth.Auth( + cfg_file=cfg_file, + username=username, + password=password, + ) + url = host.endpoint("/users/me") + resp = self.auth.request("GET", url) + self._id = resp["data"]["id"] -def check_auth(func): - """ - Some functionality of the up42 import object can theoretically be used - before authentication with UP42, so the auth needs to be checked first. - """ - @functools.wraps(func) # required for mkdocstrings - def inner(*args, **kwargs): - if _auth is None: - raise RuntimeError("Not authenticated, call up42.authenticate() first") - return func(*args, **kwargs) +workspace = _Workspace() - return inner +authenticate = workspace.authenticate -@check_auth def get_webhooks(return_json: bool = False) -> List[webhooks.Webhook]: """ Gets all registered webhooks for this workspace. @@ -68,10 +72,9 @@ def get_webhooks(return_json: bool = False) -> List[webhooks.Webhook]: Returns: A list of the registered webhooks for this workspace. """ - return webhooks.Webhooks(auth=get_auth_safely()).get_webhooks(return_json=return_json) + return webhooks.Webhooks(auth=workspace.auth, workspace_id=workspace.id).get_webhooks(return_json=return_json) -@check_auth def create_webhook( name: str, url: str, @@ -91,12 +94,11 @@ def create_webhook( Returns: A dict with details of the registered webhook. """ - return webhooks.Webhooks(auth=get_auth_safely()).create_webhook( + return webhooks.Webhooks(auth=workspace.auth, workspace_id=workspace.id).create_webhook( name=name, url=url, events=events, active=active, secret=secret ) -@check_auth def get_webhook_events() -> dict: """ Gets all available webhook events. @@ -104,10 +106,9 @@ def get_webhook_events() -> dict: Returns: A dict of the available webhook events. """ - return webhooks.Webhooks(auth=get_auth_safely()).get_webhook_events() + return webhooks.Webhooks(auth=workspace.auth, workspace_id=workspace.id).get_webhook_events() -@check_auth def get_credits_balance() -> dict: """ Display the overall credits available in your account. @@ -116,5 +117,5 @@ def get_credits_balance() -> dict: A dict with the balance of credits available in your account. """ endpoint_url = host.endpoint("/accounts/me/credits/balance") - response_json = get_auth_safely().request(request_type="GET", url=endpoint_url) + response_json = workspace.auth.request(request_type="GET", url=endpoint_url) return response_json["data"] diff --git a/up42/order.py b/up42/order.py index 96f53f206..9638fec89 100644 --- a/up42/order.py +++ b/up42/order.py @@ -129,7 +129,7 @@ def get_assets(self) -> List[asset.Asset]: raise ValueError(f"Order {self.order_id} is not FULFILLED! Current status is {self.status}") @classmethod - def place(cls, auth: up42_auth.Auth, order_parameters: dict) -> "Order": + def place(cls, auth: up42_auth.Auth, order_parameters: dict, workspace_id: str) -> "Order": """ Places an order. @@ -140,7 +140,7 @@ def place(cls, auth: up42_auth.Auth, order_parameters: dict) -> "Order": Returns: Order: The placed order. """ - url = host.endpoint(f"/v2/orders?workspaceId={auth.workspace_id}") + url = host.endpoint(f"/v2/orders?workspaceId={workspace_id}") response_json = auth.request( request_type="POST", url=url, @@ -175,7 +175,9 @@ def estimate(auth: up42_auth.Auth, order_parameters: OrderParams) -> int: ) estimated_credits: int = response_json["summary"]["totalCredits"] logger.info( - "Order is estimated to cost %s UP42 credits (order_parameters: %s)", estimated_credits, order_parameters + "Order is estimated to cost %s UP42 credits (order_parameters: %s)", + estimated_credits, + order_parameters, ) return estimated_credits diff --git a/up42/storage.py b/up42/storage.py index 6e98f3a1f..848e48003 100644 --- a/up42/storage.py +++ b/up42/storage.py @@ -39,9 +39,9 @@ class Storage: ``` """ - def __init__(self, auth: up42_auth.Auth): + def __init__(self, auth: up42_auth.Auth, workspace_id: str): self.auth = auth - self.workspace_id = auth.workspace_id + self.workspace_id = workspace_id def __repr__(self): return f"Storage(workspace_id: {self.workspace_id})" diff --git a/up42/tasking.py b/up42/tasking.py index 032015faf..8f3209bd6 100644 --- a/up42/tasking.py +++ b/up42/tasking.py @@ -1,6 +1,7 @@ """ Tasking functionality """ + import datetime from typing import List, Optional, Union @@ -24,8 +25,8 @@ class Tasking(catalog.CatalogBase): ``` """ - def __init__(self, auth: up42_auth.Auth): - super().__init__(auth) + def __init__(self, auth: up42_auth.Auth, workspace_id: str): + super().__init__(auth, workspace_id) self.type = "TASKING" def construct_order_parameters( diff --git a/up42/webhooks.py b/up42/webhooks.py index 5d8c1d984..9c8b2c673 100644 --- a/up42/webhooks.py +++ b/up42/webhooks.py @@ -17,9 +17,15 @@ class Webhook: ``` """ - def __init__(self, auth: up42_auth.Auth, webhook_id: str, webhook_info: Optional[dict] = None): + def __init__( + self, + auth: up42_auth.Auth, + workspace_id: str, + webhook_id: str, + webhook_info: Optional[dict] = None, + ): self.auth = auth - self.workspace_id = auth.workspace_id + self.workspace_id = workspace_id self.webhook_id = webhook_id if webhook_info is not None: self._info = webhook_info @@ -114,9 +120,9 @@ class Webhooks: ``` """ - def __init__(self, auth: up42_auth.Auth): + def __init__(self, auth: up42_auth.Auth, workspace_id: str): self.auth = auth - self.workspace_id = auth.workspace_id + self.workspace_id = workspace_id def get_webhook_events(self) -> dict: """ @@ -147,6 +153,7 @@ def get_webhooks(self, return_json: bool = False) -> List[Webhook]: webhooks = [ Webhook( auth=self.auth, + workspace_id=self.workspace_id, webhook_id=webhook_info["id"], webhook_info=webhook_info, ) @@ -186,6 +193,7 @@ def create_webhook( response_json = self.auth.request(request_type="POST", url=url_post, data=input_parameters) webhook = Webhook( auth=self.auth, + workspace_id=self.workspace_id, webhook_id=response_json["data"]["id"], webhook_info=response_json["data"], )