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

✨ Is686/api port schemas public api #3485

Merged
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
5 changes: 3 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ Add here YOUR checklist/notes to guide and monitor the progress of the case!

e.g.

- [ ] Openapi changes? ``make openapi-specs``, ``git commit ...`` and then ``make version-*``)
- [ ] Database migration script? ``cd packages/postgres-database``, ``make setup-commit``, ``sc-pg review -m "my changes"``
- [ ] ``make version-*``
- [ ] ``make openapi.json``
- [ ] ``cd packages/postgres-database``, ``make setup-commit``, ``sc-pg review -m "my changes"``
- [ ] Unit tests for the changes exist
- [ ] Runs in the swarm
- [ ] Documentation reflects the changes
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/ci-testing-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ jobs:
- 'packages/**'
api:
- 'api/**'
api-server:
- 'packages/**'
- 'services/api-server/**'
pcrespov marked this conversation as resolved.
Show resolved Hide resolved
autoscaling:
- 'packages/**'
- 'services/autoscaling/**'
Expand Down Expand Up @@ -1748,12 +1751,7 @@ jobs:
deploy:
name: deploy to dockerhub
if: github.event_name == 'push'
needs:
[
unit-tests,
integration-tests,
system-tests
]
needs: [unit-tests, integration-tests, system-tests]
runs-on: ${{ matrix.os }}
strategy:
matrix:
Expand Down
3 changes: 2 additions & 1 deletion services/api-server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ run-fake-devel: # starts a fake server in a dev-container
.PHONY: openapi-specs openapi.json
openapi-specs: openapi.json
openapi.json: .env
# generating openapi specs file under $<
# generating openapi specs file under $< (NOTE: Skips DEV FEATURES since this OAS is the 'offically released'!)
@set -o allexport; \
source .env; \
set +o allexport; \
export API_SERVER_DEV_FEATURES_ENABLED=0; \
python3 -c "import json; from $(APP_PACKAGE_NAME).main import *; print( json.dumps(the_app.openapi(), indent=2) )" > $@

# validates OAS file: $@
Expand Down
2 changes: 1 addition & 1 deletion services/api-server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0
0.4.1
13 changes: 10 additions & 3 deletions services/api-server/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "osparc.io web API",
"description": "osparc-simcore public web API specifications",
"version": "0.4.0",
"version": "0.4.1",
"x-logo": {
"url": "https://raw.githubusercontent.com/ITISFoundation/osparc-manual/b809d93619512eb60c827b7e769c6145758378d0/_media/osparc-logo.svg",
"altText": "osparc-simcore logo"
Expand Down Expand Up @@ -1000,7 +1000,7 @@
"solvers"
],
"summary": "Get Job Output Logfile",
"description": "Special extra output with persistent logs file for the solver run.\n\nNOTE: this is not a log stream but a predefined output that is only\navailable after the job is done.",
"description": "Special extra output with persistent logs file for the solver run.\n\nNOTE: this is not a log stream but a predefined output that is only\navailable after the job is done.\n\nNew in *version 0.4.0*",
"operationId": "get_job_output_logfile",
"parameters": [
{
Expand Down Expand Up @@ -1637,7 +1637,14 @@
"title": "Location",
"type": "array",
"items": {
"type": "string"
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
}
},
"msg": {
Expand Down
2 changes: 1 addition & 1 deletion services/api-server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.4.0
current_version = 0.4.1
commit = True
message = services/api-server version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from httpx import HTTPStatusError
from pydantic import ValidationError
from pydantic.errors import PydanticValueError
from servicelib.error_codes import create_error_code

from ...core.settings import ApplicationSettings
from ...models.schemas.solvers import Solver, SolverKeyId, VersionStr
from ...core.settings import ApplicationSettings, BasicSettings
from ...models.schemas.solvers import Solver, SolverKeyId, SolverPort, VersionStr
from ...modules.catalog import CatalogApi
from ..dependencies.application import get_reverse_url_mapper, get_settings
from ..dependencies.authentication import get_current_user_id
Expand All @@ -17,7 +18,7 @@
logger = logging.getLogger(__name__)

router = APIRouter()

settings = BasicSettings.create_from_envs()

## SOLVERS -----------------------------------------------------------------------------------------
#
Expand Down Expand Up @@ -163,3 +164,49 @@ async def get_solver_release(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Solver {solver_key}:{version} not found",
) from err


@router.get(
"/{solver_key:path}/releases/{version}/ports",
response_model=list[SolverPort],
include_in_schema=settings.API_SERVER_DEV_FEATURES_ENABLED,
)
async def list_solver_ports(
solver_key: SolverKeyId,
version: VersionStr,
user_id: int = Depends(get_current_user_id),
catalog_client: CatalogApi = Depends(get_api_client(CatalogApi)),
app_settings: ApplicationSettings = Depends(get_settings),
):
"""Lists inputs and outputs of a given solver

New in *version 0.5.0* (only with API_SERVER_DEV_FEATURES_ENABLED=1)
"""
try:

ports = await catalog_client.get_solver_ports(
user_id,
solver_key,
version,
product_name=app_settings.API_SERVER_DEFAULT_PRODUCT_NAME,
)
return ports

except ValidationError as err:
error_code = create_error_code(err)
logger.exception(
"Corrupted port data for service %s [%s]",
f"{solver_key}:{version}",
f"{error_code}",
extra={"error_code": error_code},
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Port definition of {solver_key}:{version} seems corrupted [{error_code}]",
) from err

except HTTPStatusError as err:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Ports for solver {solver_key}:{version} not found",
) from err
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ async def get_job_output_logfile(

NOTE: this is not a log stream but a predefined output that is only
available after the job is done.

New in *version 0.4.0*
"""

logs_urls: dict[NodeName, DownloadLink] = await director2_api.get_computation_logs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def init_app(settings: Optional[ApplicationSettings] = None) -> FastAPI:
api_router = create_router(settings)
app.include_router(api_router, prefix=f"/{API_VTAG}")

# NOTE: cleanup all OpenAPIs https://github.com/ITISFoundation/osparc-simcore/issues/3487
use_route_names_as_operation_ids(app)
config_all_loggers()
return app
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,33 @@ def base_url(self) -> str:
# MAIN SETTINGS --------------------------------------------


class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings):
class BasicSettings(BaseCustomSettings, MixinLoggingSettings):

# DOCKER
SC_BOOT_MODE: Optional[BootModeEnum]
# DEVELOPMENT
API_SERVER_DEV_FEATURES_ENABLED: bool = Field(
False, env=["API_SERVER_DEV_FEATURES_ENABLED", "FAKE_API_SERVER_ENABLED"]
)

# LOGGING
LOG_LEVEL: LogLevel = Field(
LogLevel.INFO.value,
env=["API_SERVER_LOGLEVEL", "LOG_LEVEL", "LOGLEVEL"],
)

# DEBUGGING
API_SERVER_REMOTE_DEBUG_PORT: int = 3000

@validator("LOG_LEVEL", pre=True)
@classmethod
def _validate_loglevel(cls, value) -> str:
return cls.validate_log_level(value)


class ApplicationSettings(BasicSettings):

# DOCKER BOOT
SC_BOOT_MODE: Optional[BootModeEnum]

# POSTGRES
API_SERVER_POSTGRES: Optional[PostgresSettings] = Field(auto_default_from_env=True)

Expand All @@ -101,10 +117,6 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings):

# DIAGNOSTICS
API_SERVER_TRACING: Optional[TracingSettings] = Field(auto_default_from_env=True)
API_SERVER_DEV_FEATURES_ENABLED: bool = Field(
False, env=["API_SERVER_DEV_FEATURES_ENABLED", "FAKE_API_SERVER_ENABLED"]
)
API_SERVER_REMOTE_DEBUG_PORT: int = 3000

@cached_property
def debug(self) -> bool:
Expand All @@ -114,8 +126,3 @@ def debug(self) -> bool:
BootModeEnum.DEVELOPMENT,
BootModeEnum.LOCAL,
]

@validator("LOG_LEVEL", pre=True)
@classmethod
def _validate_loglevel(cls, value) -> str:
return cls.validate_log_level(value)
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import urllib.parse
from typing import Optional, Union
from typing import Any, Literal, Optional, Union

import packaging.version
from models_library.basic_regex import PUBLIC_VARIABLE_NAME_RE
from models_library.services import COMPUTATIONAL_SERVICE_KEY_RE, ServiceDockerData
from packaging.version import LegacyVersion, Version
from pydantic import BaseModel, Extra, Field, HttpUrl, constr
Expand Down Expand Up @@ -104,3 +105,36 @@ def resource_name(self) -> str:
@classmethod
def compose_resource_name(cls, solver_key, solver_version) -> str:
return compose_resource_name("solvers", solver_key, "releases", solver_version)


PortKindStr = Literal["input", "output"]


class SolverPort(BaseModel):
key: str = Field(
...,
description="port identifier name",
regex=PUBLIC_VARIABLE_NAME_RE,
title="Key name",
)
kind: PortKindStr
content_schema: Optional[dict[str, Any]] = Field(
None,
description="jsonschema for the port's value. SEE https://json-schema.org",
)

class Config:
extra = Extra.ignore
schema_extra = {
"example": {
"key": "input_2",
pcrespov marked this conversation as resolved.
Show resolved Hide resolved
"kind": "input",
"content_schema": {
"title": "Sleep interval",
"type": "integer",
"x_unit": "second",
"minimum": 0,
"maximum": 5,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@

from fastapi import FastAPI
from models_library.services import ServiceDockerData, ServiceType
from pydantic import EmailStr, Extra, ValidationError
from pydantic import EmailStr, Extra, ValidationError, parse_obj_as
from settings_library.catalog import CatalogSettings

from ..models.schemas.solvers import LATEST_VERSION, Solver, SolverKeyId, VersionStr
from ..models.schemas.solvers import (
LATEST_VERSION,
Solver,
SolverKeyId,
SolverPort,
VersionStr,
)
from ..utils.client_base import BaseServiceClientApi, setup_client_instance

## from ..utils.client_decorators import JSON, handle_errors, handle_retry
Expand Down Expand Up @@ -133,6 +139,25 @@ async def get_solver(

return service.to_solver()

async def get_solver_ports(
self, user_id: int, name: SolverKeyId, version: VersionStr, *, product_name: str
):

assert version != LATEST_VERSION # nosec

service_key = urllib.parse.quote_plus(name)
service_version = version

resp = await self.client.get(
f"/services/{service_key}/{service_version}/ports",
params={"user_id": user_id},
headers={"x-simcore-products-name": product_name},
)
resp.raise_for_status()

solver_ports = parse_obj_as(list[SolverPort], resp.json())
return solver_ports

async def list_latest_releases(
self, user_id: int, *, product_name: str
) -> list[Solver]:
Expand Down
Loading