Skip to content

Commit 7f144ef

Browse files
authored
♻️ Refactors functional services package (ITISFoundation#2962)
1 parent 7530615 commit 7f144ef

File tree

25 files changed

+416
-389
lines changed

25 files changed

+416
-389
lines changed

packages/models-library/src/models_library/function_service/_utils.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

packages/models-library/src/models_library/function_service/constants.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
""" Catalog of services implemented as code instead of in a container
2+
3+
4+
"""
5+
from .api import *
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import Final
2+
3+
# NOTE: due to legacy reasons, the name remains with 'frontend' in it but
4+
# it now refers to a more general group: function sections that contains front-end services as well
5+
FUNCTION_SERVICE_KEY_PREFIX: Final[str] = "simcore/services/frontend"
6+
7+
8+
def is_function_service(service_key: str) -> bool:
9+
return service_key.startswith(f"{FUNCTION_SERVICE_KEY_PREFIX}/")
10+
11+
12+
def is_parameter_service(service_key: str) -> bool:
13+
return service_key.startswith(f"{FUNCTION_SERVICE_KEY_PREFIX}/parameter/")
14+
15+
16+
def is_iterator_service(service_key: str) -> bool:
17+
return service_key.startswith(f"{FUNCTION_SERVICE_KEY_PREFIX}/data-iterator/")
18+
19+
20+
def is_iterator_consumer_service(service_key: str) -> bool:
21+
return service_key.startswith(f"{FUNCTION_SERVICE_KEY_PREFIX}/iterator-consumer/")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Factory to build catalog of i/o metadata for functions implemented in the front-end
3+
4+
NOTE: These definitions are currently needed in the catalog and director2
5+
services. Since it is static data, instead of making a call from
6+
director2->catalog, it was decided to share as a library
7+
"""
8+
9+
import logging
10+
11+
from models_library.function_services_catalog.services import nodes_group
12+
13+
from ._settings import FunctionServiceSettings
14+
from ._utils import FunctionServices
15+
from .services import (
16+
demo_units,
17+
file_picker,
18+
iter_range,
19+
iter_sensitivity,
20+
parameters,
21+
probes,
22+
)
23+
24+
logger = logging.getLogger(__name__)
25+
26+
27+
catalog = FunctionServices(settings=FunctionServiceSettings())
28+
catalog.extend(demo_units.services)
29+
catalog.extend(file_picker.services)
30+
catalog.extend(iter_range.services)
31+
catalog.extend(iter_sensitivity.services)
32+
catalog.extend(nodes_group.services)
33+
catalog.extend(parameters.services)
34+
catalog.extend(probes.services)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import json
2+
import os
3+
4+
from pydantic import BaseSettings
5+
6+
# Expects env var: FUNCTION_SERVICES_AUTHORS='{"OM":{"name": ...}, "EN":{...} }'
7+
try:
8+
AUTHORS = json.loads(os.environ.get("FUNCTION_SERVICES_AUTHORS", "{}"))
9+
except json.decoder.JSONDecodeError:
10+
AUTHORS = {}
11+
12+
13+
class FunctionServiceSettings(BaseSettings):
14+
CATALOG_DEV_FEATURES_ENABLED: bool = False
15+
DIRECTOR_V2_DEV_FEATURES_ENABLED: bool = False
16+
WEBSERVER_DEV_FEATURES_ENABLED: bool = False
17+
18+
def is_dev_feature_enabled(self) -> bool:
19+
# NOTE that this is imported in these services
20+
# This solution is not ideal but will suffice
21+
# until function-services are moved to the database
22+
return (
23+
self.CATALOG_DEV_FEATURES_ENABLED
24+
or self.DIRECTOR_V2_DEV_FEATURES_ENABLED
25+
or self.WEBSERVER_DEV_FEATURES_ENABLED
26+
)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import logging
2+
from dataclasses import dataclass
3+
from typing import Callable, Dict, Iterable, Optional, Tuple
4+
from urllib.parse import quote
5+
6+
from models_library.basic_regex import VERSION_RE
7+
from models_library.services import SERVICE_KEY_RE
8+
from pydantic import constr
9+
10+
from ..services import Author, ServiceDockerData
11+
from ._settings import AUTHORS, FunctionServiceSettings
12+
13+
log = logging.getLogger(__name__)
14+
15+
16+
_DEFAULT = {
17+
"name": "Unknown",
18+
"email": "[email protected]",
19+
"affiliation": "unknown",
20+
}
21+
EN = Author.parse_obj(AUTHORS.get("EN", _DEFAULT))
22+
OM = Author.parse_obj(AUTHORS.get("OM", _DEFAULT))
23+
PC = Author.parse_obj(AUTHORS.get("PC", _DEFAULT))
24+
25+
26+
def create_fake_thumbnail_url(label: str) -> str:
27+
return f"https://fakeimg.pl/100x100/ff0000%2C128/000%2C255/?text={quote(label)}"
28+
29+
30+
ServiceKey = constr(regex=SERVICE_KEY_RE)
31+
ServiceVersion = constr(regex=VERSION_RE)
32+
33+
34+
@dataclass
35+
class _Record:
36+
meta: ServiceDockerData
37+
implementation: Optional[Callable] = None
38+
is_under_development: bool = False
39+
40+
41+
class FunctionServices:
42+
"""Used to register a collection of function services"""
43+
44+
def __init__(self, settings: Optional[FunctionServiceSettings] = None):
45+
self._functions: Dict[Tuple[ServiceKey, ServiceVersion], _Record] = {}
46+
self.settings = settings
47+
48+
def add_function_service(
49+
self,
50+
meta: ServiceDockerData,
51+
implementation: Optional[Callable] = None,
52+
is_under_development: bool = False,
53+
):
54+
55+
if not isinstance(meta, ServiceDockerData):
56+
raise ValueError(f"Expected ServiceDockerData, got {type(meta)}")
57+
58+
# ensure unique
59+
if (meta.key, meta.version) in self._functions:
60+
raise ValueError(f"{(meta.key, meta.version)} is already registered")
61+
62+
# TODO: ensure callable signature fits metadata
63+
64+
# register
65+
self._functions[(meta.key, meta.version)] = _Record(
66+
meta=meta,
67+
implementation=implementation,
68+
is_under_development=is_under_development,
69+
)
70+
71+
def extend(self, other: "FunctionServices"):
72+
# pylint: disable=protected-access
73+
for f in other._functions.values():
74+
self.add_function_service(f.meta, f.implementation, f.is_under_development)
75+
76+
def skip_dev(self):
77+
skip = True
78+
if self.settings:
79+
skip = not self.settings.is_dev_feature_enabled()
80+
return skip
81+
82+
def iter_items(self):
83+
skip_dev = self.skip_dev()
84+
for key, value in self._functions.items():
85+
if value.is_under_development and skip_dev:
86+
continue
87+
yield key, value
88+
89+
def iter_metadata(self) -> Iterable[ServiceDockerData]:
90+
for _, f in self.iter_items():
91+
yield f.meta
92+
93+
def iter_services_key_version(self) -> Iterable[Tuple[ServiceKey, ServiceVersion]]:
94+
for kv, f in self.iter_items():
95+
assert kv == (f.meta.key, f.meta.version) # nosec
96+
yield kv
97+
98+
def get_implementation(
99+
self, service_key: ServiceKey, service_version: ServiceVersion
100+
) -> Optional[Callable]:
101+
# raises KeyError if not found
102+
func = self._functions[(service_key, service_version)]
103+
return func.implementation
104+
105+
def get_metadata(
106+
self, service_key: ServiceKey, service_version: ServiceVersion
107+
) -> ServiceDockerData:
108+
# raises KeyError if not found
109+
func = self._functions[(service_key, service_version)]
110+
return func.meta
111+
112+
def __len__(self):
113+
return len(self._functions)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Factory to build catalog of i/o metadata for functions implemented in the front-end
3+
4+
NOTE: These definitions are currently needed in the catalog and director2
5+
services. Since it is static data, instead of making a call from
6+
director2->catalog, it was decided to share as a library
7+
"""
8+
9+
from typing import Iterator, Tuple
10+
11+
from ..services import ServiceDockerData
12+
from ._key_labels import is_function_service, is_iterator_service
13+
from ._registry import catalog
14+
15+
assert catalog # nosec
16+
assert is_iterator_service # nosec
17+
18+
19+
def iter_service_docker_data() -> Iterator[ServiceDockerData]:
20+
for meta_obj in catalog.iter_metadata():
21+
# NOTE: the originals are this way not modified from outside
22+
copied_meta_obj = meta_obj.copy(deep=True)
23+
assert is_function_service(copied_meta_obj.key) # nosec
24+
yield copied_meta_obj
25+
26+
27+
__all__: Tuple[str, ...] = (
28+
"catalog",
29+
"is_function_service",
30+
"is_iterator_service",
31+
"iter_service_docker_data",
32+
)

0 commit comments

Comments
 (0)