Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/proxy path not prefixed for v0 app #28

Merged
merged 3 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions marketplace/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ def get_app(app_id, marketplace_host_url=None, access_token=None, **kwargs):
# Getting api version and list of capabilities for the application

app_service_path = f"application-service/applications/{app_id}"
response = client.get(path=app_service_path).json()
app_api_version = parse(response["api_version"])
app_info = client.get(path=app_service_path).json()
app_api_version = parse(app_info["api_version"])

capabilities = []
for capability in response["capabilities"]:
for capability in app_info["capabilities"]:
capabilities.append(camel_to_snake(capability["name"]))

if app_api_version == parse("0.0.1"):
Expand All @@ -53,7 +53,7 @@ def get_app(app_id, marketplace_host_url=None, access_token=None, **kwargs):
**kwargs,
)
elif parse("0.0.1") < app_api_version <= parse("0.3.0"):
return _MarketPlaceApp_v0(client, capabilities, **kwargs)
return _MarketPlaceApp_v0(client, app_id, app_info, **kwargs)
else:
raise RuntimeError(f"App API version ({app_api_version}) not supported.")

Expand Down
22 changes: 15 additions & 7 deletions marketplace/app/v0/base.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import json
from typing import List
from urllib.parse import urljoin

from fastapi.responses import HTMLResponse
from marketplace_standard_app_api.models.system import GlobalSearchResponse

from marketplace.client import MarketPlaceClient

from ..utils import check_capability_availability
from ..utils import camel_to_snake, check_capability_availability


class _MarketPlaceAppBase:
def __init__(self, client: MarketPlaceClient, capabilities: List):
def __init__(self, client: MarketPlaceClient, app_id: str, app_info: dict):
self._client: MarketPlaceClient = client
self.capabilities = capabilities # FOR DEBUGGING
self.app_id: str = app_id
self._app_info: dict = app_info
self.capabilities = {
camel_to_snake(c["name"]) for c in app_info["capabilities"]
}

def _proxy_path(self, path):
return urljoin(f"mp-api/proxy/{self.app_id}/", path)

@check_capability_availability
def frontend(self) -> HTMLResponse:
return self._client.get(path="frontend")
return self._client.get(path=self._proxy_path("frontend"))

@check_capability_availability
def heartbeat(self) -> HTMLResponse:
return self._client.get(path="heartbeat")
return self._client.get(path=self._proxy_path("heartbeat"))

@check_capability_availability
def global_search(
Expand All @@ -29,7 +36,8 @@ def global_search(
return GlobalSearchResponse.parse_obj(
json.loads(
self._client.get(
"globalSearch", params={"q": q, "limit": limit, "offset": offset}
self._proxy_path("globalSearch"),
params={"q": q, "limit": limit, "offset": offset},
)
)
)
34 changes: 19 additions & 15 deletions marketplace/app/v0/object_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def list_collections(
) -> object_storage.CollectionListResponse:
return object_storage.CollectionListResponse(
**self._client.get(
"listCollections", params={"limit": limit, "offset": offset}
self._proxy_path("listCollections"),
params={"limit": limit, "offset": offset},
).json()
)

Expand All @@ -29,7 +30,7 @@ def list_datasets(
) -> object_storage.DatasetListResponse:
return object_storage.DatasetListResponse(
**self._client.get(
"listDatasets",
self._proxy_path("listDatasets"),
params={
"collection_name": collection_name,
"limit": limit,
Expand All @@ -45,15 +46,16 @@ def create_or_update_collection(
collection_name: object_storage.CollectionName = None,
) -> str:
return self._client.put(
"createOrUpdateCollection",
self._proxy_path("createOrUpdateCollection"),
params={"collection_name": collection_name} if collection_name else {},
headers=_encode_metadata(metadata),
).text

@check_capability_availability
def delete_collection(self, collection_name: object_storage.CollectionName):
self._client.delete(
"deleteCollection", params={"collection_name": collection_name}
self._proxy_path("deleteCollection"),
params={"collection_name": collection_name},
)

# NOTE: change to GET for the meeting if proxy doesn't support HEAD requests
Expand All @@ -62,7 +64,8 @@ def get_collection_metadata(
self, collection_name: object_storage.CollectionName
) -> Union[Dict, str]:
response_headers: dict = self._client.head(
"getCollectionMetadata", params={"collection_name": collection_name}
self._proxy_path("getCollectionMetadata"),
params={"collection_name": collection_name},
).headers
return json.dumps(_decode_metadata(headers=response_headers))

Expand All @@ -73,7 +76,7 @@ def create_collection(
metadata: dict = None,
) -> str:
return self._client.put(
"createCollection",
self._proxy_path("createCollection"),
params={"collection_name": collection_name} if collection_name else {},
headers=_encode_metadata(metadata),
).text
Expand All @@ -92,7 +95,7 @@ def create_dataset(
return object_storage.DatasetCreateResponse.parse_obj(
json.loads(
self._client.put(
"createDataset",
self._proxy_path("createDataset"),
params=params,
headers=_encode_metadata(metadata),
data=file.file,
Expand All @@ -111,7 +114,7 @@ def create_dataset_metadata(
if dataset_name:
params.update({"dataset_name": dataset_name})
return self._client.post(
"createDatasetMetadata",
self._proxy_path("createDatasetMetadata"),
params=params,
headers=_encode_metadata(metadata),
).text
Expand All @@ -123,7 +126,7 @@ def get_dataset(
dataset_name: object_storage.DatasetName,
) -> Union[Dict, str]:
return self._client.get(
"getDataset",
self._proxy_path("getDataset"),
params={"collection_name": collection_name, "dataset_name": dataset_name},
).json()

Expand All @@ -139,7 +142,7 @@ def create_or_replace_dataset(
params.update({"dataset_name": dataset_name})
return object_storage.DatasetCreateResponse(
**self._client.put(
"createOrReplaceDataset",
self._proxy_path("createOrReplaceDataset"),
params=params,
headers=_encode_metadata(metadata),
data=file.file,
Expand All @@ -154,7 +157,7 @@ def create_or_replace_dataset_metadata(
metadata: dict = None,
) -> str:
return self._client.put(
"createOrReplaceDatasetMetadata",
self._proxy_path("createOrReplaceDatasetMetadata"),
params={"collection_name": collection_name, "dataset_name": dataset_name},
headers=_encode_metadata(metadata),
).text
Expand All @@ -166,7 +169,7 @@ def delete_dataset(
dataset_name: object_storage.DatasetName,
):
self._client.delete(
"deleteDataset",
self._proxy_path("deleteDataset"),
params={"collection_name": collection_name, "dataset_name": dataset_name},
)

Expand All @@ -178,7 +181,7 @@ def get_dataset_metadata(
dataset_name: object_storage.DatasetName,
) -> Union[Dict, str]:
response_headers: dict = self._client.head(
"getDatasetMetadata",
self._proxy_path("getDatasetMetadata"),
params={"collection_name": collection_name, "dataset_name": dataset_name},
).headers
return json.dumps(_decode_metadata(headers=response_headers))
Expand All @@ -189,7 +192,8 @@ def list_semantic_mappings(
) -> object_storage.SemanticMappingListResponse:
return object_storage.SemanticMappingListResponse(
**self._client.get(
"listSemanticMappings", params={"limit": limit, "offset": offset}
self._proxy_path("listSemanticMappings"),
params={"limit": limit, "offset": offset},
).json()
)

Expand All @@ -200,7 +204,7 @@ def get_semantic_mapping(
return object_storage.SemanticMappingModel.parse_obj(
json.loads(
self._client.get(
"getSemanticMapping",
self._proxy_path("getSemanticMapping"),
params={"semantic_mapping_id": semantic_mapping_id},
)
)
Expand Down
19 changes: 13 additions & 6 deletions marketplace/app/v0/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def get_transformation_list(
return transformation.TransformationListResponse.parse_obj(
json.loads(
self._client.get(
"getTransformationList", params={"limit": limit, "offset": offset}
self._proxy_path("getTransformationList"),
params={"limit": limit, "offset": offset},
)
)
)
Expand All @@ -24,7 +25,11 @@ def new_transformation(
self, new_transformation: transformation.NewTransformationModel
) -> transformation.TransformationCreateResponse:
return transformation.TransformationCreateResponse.parse_obj(
json.loads(self._client.post("newTransformation", json=new_transformation))
json.loads(
self._client.post(
self._proxy_path("newTransformation"), json=new_transformation
)
)
)

@check_capability_availability
Expand All @@ -34,15 +39,17 @@ def get_transformation(
return transformation.TransformationModel.parse_obj(
json.loads(
self._client.get(
"getTransformation", params={"transformation_id": transformation_id}
self._proxy_path("getTransformation"),
params={"transformation_id": transformation_id},
)
)
)

@check_capability_availability
def delete_transformation(self, transformation_id: transformation.TransformationId):
self._client.delete(
"deleteTransformation", params={"transformation_id": transformation_id}
self._proxy_path("deleteTransformation"),
params={"transformation_id": transformation_id},
)

@check_capability_availability("update_transformation")
Expand All @@ -54,7 +61,7 @@ def _update_transformation(
return transformation.TransformationUpdateResponse.parse_obj(
json.loads(
self._client.patch(
"updateTransformation",
self._proxy_path("updateTransformation"),
params={"transformation_id": transformation_id},
json=update,
)
Expand Down Expand Up @@ -102,7 +109,7 @@ def get_transformation_state(
return transformation.TransformationStateResponse.parse_obj(
json.loads(
self._client.get(
"getTransformationState",
self._proxy_path("getTransformationState"),
params={"transformation_id": transformation_id},
)
)
Expand Down
5 changes: 4 additions & 1 deletion marketplace/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@

from .version import __version__

MP_DEFAULT_HOST = "https://www.materials-marketplace.eu/"


class MarketPlaceClient:
"""Interact with the MarketPlace platform."""

def __init__(self, marketplace_host_url=None, access_token=None):
marketplace_host_url = marketplace_host_url or os.environ.get(
"MP_HOST", "https://www.materials-marketplace.eu/"
"MP_HOST",
MP_DEFAULT_HOST,
)
access_token = access_token or os.environ["MP_ACCESS_TOKEN"]

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pre_commit =
pre-commit==2.15.0
tests =
pytest==6.2.5
requests-mock==1.9.3

[flake8]
ignore =
Expand Down
45 changes: 39 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,48 @@
Provide fixtures for all tests.
"""

import re
from urllib.parse import urljoin

import pytest

from marketplace.client import MP_DEFAULT_HOST


@pytest.fixture
def _app_service_response():
return {
"api_version": "0.3.0",
"capabilities": [
{"name": "frontend"},
{"name": "getObject"},
{"name": "heartbeat"},
],
}


@pytest.fixture
def environment(monkeypatch):
# Full tests will eventually require a local development deployment of the
# MarketPlace platform.
monkeypatch.setenv("MP_HOST", "https://lvh.me")
# For now, we are not providing a mock marketplace, all tests will
# therefore be runnable with empty tokens.
def _app_service(requests_mock, _app_service_response):
application_service = re.compile(
f"{MP_DEFAULT_HOST}application-service/applications/.*"
)

requests_mock.get(application_service, json=_app_service_response)


@pytest.fixture
def _proxy_service(requests_mock):
proxy_path = "mp-api/proxy"
requests_mock.get(
re.compile(urljoin(MP_DEFAULT_HOST, proxy_path) + r"/.*/frontend"),
text="<html><body>Hello app!</body></html>",
)
requests_mock.get(
re.compile(urljoin(MP_DEFAULT_HOST, proxy_path) + r"/.*/heartbeat"), text="OK"
)


@pytest.fixture(autouse=True)
def environment(monkeypatch, _app_service, _proxy_service):
monkeypatch.setenv("MP_ACCESS_TOKEN", "")
monkeypatch.setenv("MP_REFRESH_TOKEN", "")
24 changes: 24 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

from marketplace.app import MarketPlaceApp, get_app


@pytest.fixture
def app():
return get_app("test-app")


def test_app_v0_0_1():
with pytest.warns(UserWarning):
app = MarketPlaceApp(client_id="test-app", capabilities=["heartbeat"])
assert app.heartbeat() == "OK"


def test_app_v0(app):
assert "heartbeat" in app.capabilities
response = app.heartbeat()
assert response.ok
assert "frontend" in app.capabilities
response = app.frontend()
assert response.ok
assert "Hello app!" in response.text