Skip to content

Commit

Permalink
[PT-5198] Workspace singleton (#595)
Browse files Browse the repository at this point in the history
* Adding workspace singleton
* Removing from auth the workspace.
* Removing unnecessary fixture from test_main
* updating changelog and version
* Simplify test_auth setup
* Simplify auth fixtures and workspace properties
* Test improvements
* Authenticate module export
* Improve workspace ID injection
* Apply hooks
* Version Bump
---------

Co-authored-by: Javid Gafar-zada <[email protected]>
  • Loading branch information
andher1802 and javidq authored May 24, 2024
1 parent 03909f1 commit c3cdf51
Show file tree
Hide file tree
Showing 23 changed files with 231 additions and 233 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
license = "https://github.com/up42/up42-py/blob/master/LICENSE"
Expand Down
14 changes: 2 additions & 12 deletions tests/fixtures/fixtures_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 3 additions & 3 deletions tests/fixtures/fixtures_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
2 changes: 0 additions & 2 deletions tests/fixtures/fixtures_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,5 +269,3 @@
"updatedAt": "2022-06-20T04:05:31.755744Z",
}
}

JSON_BALANCE = {"data": {"balance": 10693}}
2 changes: 1 addition & 1 deletion tests/fixtures/fixtures_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 3 additions & 3 deletions tests/fixtures/fixtures_tasking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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)
20 changes: 12 additions & 8 deletions tests/fixtures/fixtures_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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": [
{
Expand Down Expand Up @@ -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)
67 changes: 26 additions & 41 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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",
Expand All @@ -114,45 +105,40 @@ 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,
request_headers=REQUEST_HEADERS,
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,
request_headers=REQUEST_HEADERS,
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,
request_headers=REQUEST_HEADERS,
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,
Expand All @@ -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)

Expand All @@ -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,
Expand All @@ -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 "")
5 changes: 2 additions & 3 deletions tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}],
Expand All @@ -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,
):
Expand Down
61 changes: 27 additions & 34 deletions tests/test_initialization.py
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit c3cdf51

Please sign in to comment.