Skip to content

Commit 18f6042

Browse files
authored
Fixes on catalog/services section of the web-API (#2151)
Requests in the catalog/services section of the web-API are validated in the webserver and forwarded to the catalog. The responses of the catalog are then again processed in the webserver (adds convenience values for the units) and retransmitted back to the front-end. Related to #2141: Processing of metadata in pipelines: units * catalog OAS published under api/specs/catalog * Adds constraint on uvloop (dropping support for 3.6!) * full upgrade of catalog reqs * Minor cleanup .env file * Increases test coverage * Cleanup tests: reorganize and delete old Adds tests for models * Added examples for testing * Adds a layer in the web-server that extends response of catalog * Serialization done with orjson Adds tests on replacement * fixes fixtures * Fixes example * Fixes catalog OAS failure * Prunes defaultValue in output ports * Change title of web API * Catalog entrypoints response model excludes None fields * @sanderegg review: restored return annotations and added some more for redemption :-) * @sanderegg review: policy drops only unset values instead of nulls for responses Units are formatted ONLY when provided * Fixes public-api test
1 parent d6750a3 commit 18f6042

File tree

35 files changed

+969
-448
lines changed

35 files changed

+969
-448
lines changed

.env-devel

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
#
2-
# - Keep it alfphabetical order and grouped by prefix
2+
# - Keep it alfphabetical order and grouped by prefix [see vscode cmd: Sort Lines Ascending]
33
# - To expose: export $(grep -v '^#' .env | xargs -0)
44
#
5+
56
API_SERVER_DEV_FEATURES_ENABLED=0
67

78
BF_API_KEY=none
89
BF_API_SECRET=none
910

11+
DIRECTOR_REGISTRY_CACHING_TTL=900
12+
DIRECTOR_REGISTRY_CACHING=True
13+
1014
POSTGRES_DB=simcoredb
1115
POSTGRES_ENDPOINT=postgres:5432
1216
POSTGRES_HOST=postgres
1317
POSTGRES_PASSWORD=adminadmin
1418
POSTGRES_PORT=5432
1519
POSTGRES_USER=scu
1620

17-
RABBIT_HOST=rabbit
1821
RABBIT_CHANNELS={"log": "comp.backend.channels.log", "instrumentation": "comp.backend.channels.instrumentation"}
22+
RABBIT_HOST=rabbit
1923
RABBIT_PASSWORD=adminadmin
2024
RABBIT_PORT=5672
2125
RABBIT_USER=admin
@@ -28,12 +32,10 @@ REGISTRY_PW=adminadmin
2832
REGISTRY_SSL=True
2933
REGISTRY_URL=registry.osparc-master.speag.com
3034
REGISTRY_USER=admin
31-
DIRECTOR_REGISTRY_CACHING=True
32-
DIRECTOR_REGISTRY_CACHING_TTL=900
3335

36+
# NOTE: 172.17.0.1 is the docker0 interface, which redirect from inside a container onto the host network interface.
3437
S3_ACCESS_KEY=12345678
3538
S3_BUCKET_NAME=simcore
36-
# 172.17.0.1 is the docker0 interface, which redirect from inside a container onto the host network interface.
3739
S3_ENDPOINT=172.17.0.1:9001
3840
S3_SECRET_KEY=12345678
3941
S3_SECURE=0
@@ -51,25 +53,25 @@ TRACING_ZIPKIN_ENDPOINT=http://jaeger:9411
5153

5254
TRAEFIK_SIMCORE_ZONE=internal_simcore_stack
5355

56+
# NOTE: WEBSERVER_SESSION_SECRET_KEY = $(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key())")
5457
WEBSERVER_DEV_FEATURES_ENABLED=0
5558
WEBSERVER_HOST=webserver
5659
WEBSERVER_LOGIN_REGISTRATION_CONFIRMATION_REQUIRED=0
5760
WEBSERVER_LOGIN_REGISTRATION_INVITATION_REQUIRED=0
58-
# python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key())"
59-
WEBSERVER_SESSION_SECRET_KEY=REPLACE ME with a key of at least length 32.
60-
WEBSERVER_STUDIES_ACCESS_ENABLED=0
61+
WEBSERVER_FEEDBACK_FORM_URL=https://docs.google.com/forms/d/e/1FAIpQLSe232bTigsM2zV97Kjp2OhCenl6o9gNGcDFt2kO_dfkIjtQAQ/viewform?usp=sf_link
62+
WEBSERVER_FOGBUGZ_LOGIN_URL=https://z43.manuscript.com/login
63+
WEBSERVER_FOGBUGZ_NEWCASE_URL=https://z43.manuscript.com/f/cases/new?command=new&pg=pgEditBug&ixProject=45&ixArea=449
64+
WEBSERVER_GARBAGE_COLLECTION_INTERVAL_SECONDS=30
65+
WEBSERVER_MANUAL_EXTRA_URL=https://itisfoundation.github.io/osparc-manual-z43/
66+
WEBSERVER_MANUAL_MAIN_URL=http://docs.osparc.io/
67+
WEBSERVER_PROMETHEUS_API_VERSION=v1
6168
WEBSERVER_PROMETHEUS_HOST=http://prometheus
6269
WEBSERVER_PROMETHEUS_PORT=9090
63-
WEBSERVER_PROMETHEUS_API_VERSION=v1
6470
WEBSERVER_RESOURCES_DELETION_TIMEOUT_SECONDS=900
65-
WEBSERVER_GARBAGE_COLLECTION_INTERVAL_SECONDS=30
66-
WEBSERVER_MANUAL_MAIN_URL=http://docs.osparc.io/
67-
WEBSERVER_MANUAL_EXTRA_URL=https://itisfoundation.github.io/osparc-manual-z43/
68-
WEBSERVER_FOGBUGZ_LOGIN_URL=https://z43.manuscript.com/login
69-
WEBSERVER_FOGBUGZ_NEWCASE_URL=https://z43.manuscript.com/f/cases/new?command=new&pg=pgEditBug&ixProject=45&ixArea=449
7071
WEBSERVER_S4L_FOGBUGZ_NEWCASE_URL=https://z43.manuscript.com/f/cases/new?command=new&pg=pgEditBug&ixProject=45&ixArea=458
72+
WEBSERVER_SESSION_SECRET_KEY=REPLACE ME with a key of at least length 32.
73+
WEBSERVER_STUDIES_ACCESS_ENABLED=0
7174
WEBSERVER_TIS_FOGBUGZ_NEWCASE_URL=https://z43.manuscript.com/f/cases/new?command=new&pg=pgEditBug&ixProject=45&ixArea=459
72-
WEBSERVER_FEEDBACK_FORM_URL=https://docs.google.com/forms/d/e/1FAIpQLSe232bTigsM2zV97Kjp2OhCenl6o9gNGcDFt2kO_dfkIjtQAQ/viewform?usp=sf_link
7375
WEBSERVER_VIEWER_RAWGRAPH_VERSION=2.11.1
7476
WEBSERVER_VIEWER_SIM4LIFE_VERSION=1.0.29
7577

api/specs/webserver/openapi-catalog.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,18 @@ paths:
6666
"422":
6767
description: Validation Error
6868

69-
/catalog/services:
69+
catalog_services:
7070
get:
7171
tags:
7272
- catalog
7373
summary: List Services
74-
operationId: list_catalog_services
74+
operationId: list_services_handler
7575
responses:
7676
"200":
7777
description: Returns list of services from the catalog
7878
default:
7979
$ref: "./openapi.yaml#/components/responses/DefaultErrorResponse"
80-
/catalog/services/{service_key}/{service_version}:
80+
catalog_services_service_key_service_version:
8181
parameters:
8282
- in: path
8383
name: service_key
@@ -97,7 +97,7 @@ paths:
9797
tags:
9898
- catalog
9999
summary: Get Service
100-
operationId: get_catalog_service
100+
operationId: get_service_handler
101101
responses:
102102
"200":
103103
description: Returns service
@@ -107,7 +107,7 @@ paths:
107107
tags:
108108
- catalog
109109
summary: Update Service
110-
operationId: update_catalog_service
110+
operationId: update_service_handler
111111
requestBody:
112112
content:
113113
application/json:

api/specs/webserver/openapi.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
openapi: 3.0.0
22
info:
3-
title: "osparc-simcore RESTful API"
3+
title: "osparc-simcore web API"
44
version: 0.6.0
5-
description: "RESTful API designed for web clients"
5+
description: "API designed for the front-end app"
66
contact:
77
name: IT'IS Foundation
88
@@ -218,11 +218,11 @@ paths:
218218
$ref: "./openapi-catalog.yaml#/paths/~1catalog~1dags"
219219
/catalog/dags/{dag_id}:
220220
$ref: "./openapi-catalog.yaml#/paths/~1catalog~1dags~1{dag_id}"
221+
221222
/catalog/services:
222-
$ref: "./openapi-catalog.yaml#/paths/~1catalog~1services"
223+
$ref: "./openapi-catalog.yaml#/paths/catalog_services"
223224
/catalog/services/{service_key}/{service_version}:
224-
$ref: "./openapi-catalog.yaml#/paths/~1catalog~1services~1{service_key}~1{service_version}"
225-
225+
$ref: "./openapi-catalog.yaml#/paths/catalog_services_service_key_service_version"
226226
/catalog/services/{service_key}/{service_version}/inputs:
227227
$ref: "./openapi-catalog.yaml#/paths/catalog_services_service_key_service_version_inputs"
228228
/catalog/services/{service_key}/{service_version}/inputs/{input_key}:

api/tests/conftest.py

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,98 +10,107 @@
1010
from pathlib import Path
1111

1212
import pytest
13-
1413
from utils import is_json_schema, load_specs
1514

1615
log = logging.getLogger(__name__)
1716

1817
# Conventions
19-
COMMON = 'common'
20-
OPENAPI_MAIN_FILENAME = 'openapi.yaml'
18+
COMMON = "common"
19+
OPENAPI_MAIN_FILENAME = "openapi.yaml"
2120

2221
current_dir = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent
2322

2423

25-
@pytest.fixture(scope='session')
26-
def this_repo_root_dir():
24+
@pytest.fixture(scope="session")
25+
def this_repo_root_dir() -> Path:
2726
root_dir = current_dir.parent.parent
2827
assert root_dir
2928
assert any(root_dir.glob(".git"))
3029
return root_dir
3130

3231

33-
@pytest.fixture(scope='session')
34-
def api_specs_dir():
32+
@pytest.fixture(scope="session")
33+
def api_specs_dir() -> Path:
3534
return current_dir.parent / "specs"
3635

3736

3837
@pytest.fixture(scope="session")
39-
def webserver_api_dir(api_specs_dir):
38+
def webserver_api_dir(api_specs_dir) -> Path:
4039
return api_specs_dir / "webserver"
4140

42-
@pytest.fixture(scope='session')
41+
42+
@pytest.fixture(scope="session")
4343
def api_specs_info(api_specs_dir):
4444
"""
45-
Returns a namedtuple with info on every
45+
Returns a namedtuple with info on every
4646
"""
47-
service_dirs = [d for d in api_specs_dir.iterdir() if d.is_dir() and not d.name.endswith(COMMON)]
47+
service_dirs = [
48+
d for d in api_specs_dir.iterdir() if d.is_dir() and not d.name.endswith(COMMON)
49+
]
4850

49-
info_cls = namedtuple("ApiSpecsInfo", "service version openapi_path url_path".split())
51+
info_cls = namedtuple(
52+
"ApiSpecsInfo", "service version openapi_path url_path".split()
53+
)
5054
info = []
5155
for srv_dir in service_dirs:
52-
version_dirs = [d for d in srv_dir.iterdir() if d.is_dir() and not d.name.endswith(COMMON)]
56+
version_dirs = [
57+
d for d in srv_dir.iterdir() if d.is_dir() and not d.name.endswith(COMMON)
58+
]
5359
for ver_dir in version_dirs:
5460
openapi_path = ver_dir / OPENAPI_MAIN_FILENAME
5561
if openapi_path.exists():
56-
info.append( info_cls(
57-
service=srv_dir.name,
58-
version=ver_dir.name,
59-
openapi_path=openapi_path,
60-
url_path=relpath(openapi_path, srv_dir) # ${version}/openapi.yaml
61-
))
62+
info.append(
63+
info_cls(
64+
service=srv_dir.name,
65+
version=ver_dir.name,
66+
openapi_path=openapi_path,
67+
url_path=relpath(
68+
openapi_path, srv_dir
69+
), # ${version}/openapi.yaml
70+
)
71+
)
6272
# https://yarl.readthedocs.io/en/stable/api.html#yarl.URL
6373
# [scheme:]//[user[:password]@]host[:port][/path][?query][#fragment]
6474
return info
6575

6676

6777
@pytest.fixture(scope="session")
6878
def all_api_specs_tails(api_specs_dir):
69-
""" Returns openapi/jsonschema spec files path relative to specs_dir
70-
71-
"""
79+
"""Returns openapi/jsonschema spec files path relative to specs_dir"""
7280
return _all_api_specs_tails_impl(api_specs_dir)
7381

82+
7483
def _all_api_specs_tails_impl(api_specs_dir):
7584
tails = []
76-
for fpath in chain(*[api_specs_dir.rglob(wildcard) for wildcard in ("*.json", "*.y*ml")]):
85+
for fpath in chain(
86+
*[api_specs_dir.rglob(wildcard) for wildcard in ("*.json", "*.y*ml")]
87+
):
7788
tail = relpath(fpath, api_specs_dir)
78-
tails.append(Path(tail) )
89+
tails.append(Path(tail))
7990
return tails
8091

8192

8293
def list_openapi_tails():
83-
""" Returns relative path to all non-jsonschema (i.e. potential openapi)
94+
"""Returns relative path to all non-jsonschema (i.e. potential openapi)
8495
85-
SEE api_specs_tail to get one at a time
96+
SEE api_specs_tail to get one at a time
8697
"""
8798
tails = []
8899
specs_dir = current_dir.parent / "specs"
89100
for tail in _all_api_specs_tails_impl(specs_dir):
90-
specs = load_specs( specs_dir / tail)
101+
specs = load_specs(specs_dir / tail)
91102
if not is_json_schema(specs):
92-
tails.append( str(tail) )
103+
tails.append(str(tail))
93104
return tails
94105

95106

96-
@pytest.fixture(scope="session",
97-
params=list_openapi_tails()
98-
)
107+
@pytest.fixture(scope="session", params=list_openapi_tails())
99108
def api_specs_tail(request, api_specs_dir):
100-
""" Returns api specs file path relative to api_specs_dir
109+
"""Returns api specs file path relative to api_specs_dir
101110
102-
NOTE: this is a parametrized fixture that
103-
represents one api-specs tail at a time!
104-
NOTE: as_str==True, so it gets printed
111+
NOTE: this is a parametrized fixture that
112+
represents one api-specs tail at a time!
113+
NOTE: as_str==True, so it gets printed
105114
"""
106115
specs_tail = request.param
107116
assert exists(api_specs_dir / specs_tail)

packages/models-library/src/models_library/projects_nodes.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ class Node(BaseModel):
125125
run_hash: Optional[str] = Field(
126126
None,
127127
description="the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated",
128-
examples=["a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2"],
129128
alias="runHash",
130129
)
131130

@@ -158,7 +157,6 @@ class Node(BaseModel):
158157
parent: Optional[NodeID] = Field(
159158
None,
160159
description="Parent's (group-nodes') node ID s. Used to group",
161-
examples=["nodeUUid1", "nodeUuid2"],
162160
)
163161

164162
# NOTE: use projects_ui.py

packages/models-library/src/models_library/services.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,13 @@ class ServiceAccessRights(BaseModel):
363363

364364

365365
class ServiceMetaData(ServiceCommonData):
366-
# for a partial update all members must be Optional
366+
# Overrides all fields of ServiceCommonData:
367+
# - for a partial update all members must be Optional
367368
name: Optional[str]
368369
thumbnail: Optional[HttpUrl]
369370
description: Optional[str]
371+
372+
# user-defined metatada
370373
classifiers: Optional[List[str]]
371374
quality: Dict[str, Any] = {}
372375

packages/service-library/tests/conftest.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
import sys
66
from pathlib import Path
7+
from typing import Dict
78

89
import pytest
9-
import yaml
10-
1110
import servicelib
1211
from servicelib.openapi import create_openapi_specs
1312

@@ -18,14 +17,14 @@ def here():
1817

1918

2019
@pytest.fixture(scope="session")
21-
def package_dir():
20+
def package_dir() -> Path:
2221
pdir = Path(servicelib.__file__).resolve().parent
2322
assert pdir.exists()
2423
return pdir
2524

2625

2726
@pytest.fixture(scope="session")
28-
def osparc_simcore_root_dir(here):
27+
def osparc_simcore_root_dir(here) -> Path:
2928
root_dir = here.parent.parent.parent.resolve()
3029
assert root_dir.exists(), "Is this service within osparc-simcore repo?"
3130
assert any(root_dir.glob("packages/service-library")), (
@@ -35,13 +34,13 @@ def osparc_simcore_root_dir(here):
3534

3635

3736
@pytest.fixture
38-
def petstore_spec_file(here):
37+
def petstore_spec_file(here) -> Path:
3938
filepath = here / "data/oas3/petstore.yaml"
4039
assert filepath.exists()
4140
return filepath
4241

4342

4443
@pytest.fixture
45-
async def petstore_specs(loop, petstore_spec_file):
44+
async def petstore_specs(loop, petstore_spec_file) -> Dict:
4645
specs = await create_openapi_specs(petstore_spec_file)
4746
return specs

requirements/constraints.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ idna<3,>=2.5
3636

3737
# vulnerability https://github.com/advisories/GHSA-rhm9-p9w5-fwm7 Feb.2021
3838
cryptography>=3.3.2
39+
40+
41+
# constraint since https://github.com/MagicStack/uvloop/releases/tag/v0.15.0: drops support for 3.5/3.6 Feb.2021
42+
uvloop<0.15.0 ; python_version < '3.7'

scripts/common-service.Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ shell: ## runs shell inside $(APP_NAME) container
7070

7171
.PHONY: tail
7272
tail: ## tails log of $(APP_NAME) container
73-
docker logs --follow $(shell docker ps -f "name=$(APP_NAME)*" --format {{.ID}}) 2>&1
73+
docker logs --follow $(shell docker ps --filter "name=$(APP_NAME)*" --format {{.ID}}) 2>&1
7474

7575

7676
.PHONY: stats

services/api-server/tests/unit/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ def project_env_devel_environment(project_env_devel_dict, monkeypatch):
7575

7676

7777
@pytest.fixture(scope="session")
78-
def project_slug_dir():
78+
def project_slug_dir() -> Path:
7979
folder = current_dir.parent.parent
8080
assert folder.exists()
8181
assert any(folder.glob("src/simcore_service_api_server"))
8282
return folder
8383

8484

8585
@pytest.fixture(scope="session")
86-
def package_dir():
86+
def package_dir() -> Path:
8787
"""Notice that this might be under src (if installed as edit mode)
8888
or in the installation folder
8989
"""

0 commit comments

Comments
 (0)