Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
56 changes: 53 additions & 3 deletions services/api-server/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,7 @@
"programs"
],
"summary": "Create Program Job",
"description": "Creates a job in a specific release with given inputs.\n\nNOTE: This operation does **not** start the job",
"description": "Creates a program job",
"operationId": "create_program_job",
"security": [
{
Expand Down Expand Up @@ -1384,6 +1384,15 @@
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_create_program_job_v0_programs__program_key__releases__version__jobs_post"
}
}
}
},
"responses": {
"201": {
"description": "Successful Response",
Expand Down Expand Up @@ -6056,6 +6065,34 @@
],
"title": "Body_complete_multipart_upload_v0_files__file_id__complete_post"
},
"Body_create_program_job_v0_programs__program_key__releases__version__jobs_post": {
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Name"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Description"
}
},
"type": "object",
"title": "Body_create_program_job_v0_programs__program_key__releases__version__jobs_post"
},
"Body_upload_file_v0_files_content_put": {
"properties": {
"file": {
Expand Down Expand Up @@ -7715,14 +7752,26 @@
],
"title": "Url",
"description": "Link to get this resource"
},
"version_display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Version Display"
}
},
"type": "object",
"required": [
"id",
"version",
"title",
"url"
"url",
"version_display"
],
"title": "Program",
"description": "A released program with a specific version",
Expand All @@ -7732,7 +7781,8 @@
"maintainer": "[email protected]",
"title": "Sim4life",
"url": "https://api.osparc.io/v0/solvers/simcore%2Fservices%2Fdynamic%2Fsim4life/releases/8.0.0",
"version": "8.0.0"
"version": "8.0.0",
"version_display": "8.0.0"
}
},
"RunningState": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@

from fastapi import Depends
from models_library.api_schemas_webserver.projects import ProjectCreateNew, ProjectGet
from models_library.products import ProductName
from models_library.projects import ProjectID
from models_library.projects_nodes_io import NodeID
from models_library.users import UserID
from pydantic import HttpUrl
from servicelib.fastapi.app_state import SingletonInAppStateMixin
from servicelib.logging_utils import log_context
from simcore_service_api_server.api.dependencies.authentication import (
get_current_user_id,
get_product_name,
)
from simcore_service_api_server.api.dependencies.webserver_rpc import (
get_wb_api_rpc_client,
)
from simcore_service_api_server.services_rpc.wb_api_server import WbApiRpcClient

from .api.dependencies.webserver_http import get_webserver_session
from .models.schemas.jobs import Job, JobInputs
Expand All @@ -26,11 +36,22 @@
class JobService(SingletonInAppStateMixin):
app_state_name = "JobService"
_web_rest_api: AuthSession
_web_rpc_api: WbApiRpcClient
_user_id: UserID
_product_name: ProductName

def __init__(
self, web_rest_api: Annotated[AuthSession, Depends(get_webserver_session)]
self,
*,
web_rest_api: Annotated[AuthSession, Depends(get_webserver_session)],
web_rpc_api: Annotated[WbApiRpcClient, Depends(get_wb_api_rpc_client)],
user_id: Annotated[UserID, Depends(get_current_user_id)],
product_name: Annotated[ProductName, Depends(get_product_name)],
):
self._web_rest_api = web_rest_api
self._web_rpc_api = web_rpc_api
self._user_id = user_id
self._product_name = product_name

async def create_job(
self,
Expand All @@ -41,6 +62,8 @@ async def create_job(
parent_node_id: NodeID | None,
url_for: Callable[..., HttpUrl],
hidden: bool,
name: str | None,
description: str | None,
) -> tuple[Job, ProjectGet]:
# creates NEW job as prototype
pre_job = Job.create_job_from_solver_or_program(
Expand All @@ -50,14 +73,24 @@ async def create_job(
logger=_logger, level=logging.DEBUG, msg=f"Creating job {pre_job.name}"
):
project_in: ProjectCreateNew = create_new_project_for_job(
solver_or_program, pre_job, inputs
solver_or_program=solver_or_program,
job=pre_job,
inputs=inputs,
description=description,
name=name,
)
new_project: ProjectGet = await self._web_rest_api.create_project(
project_in,
is_hidden=hidden,
parent_project_uuid=parent_project_uuid,
parent_node_id=parent_node_id,
)
await self._web_rpc_api.mark_project_as_job(
product_name=self._product_name,
user_id=self._user_id,
project_uuid=new_project.uuid,
job_parent_resource_name=pre_job.runner_name,
)

assert new_project # nosec
assert new_project.uuid == pre_job.id # nosec
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# pylint: disable=too-many-arguments
import logging
from collections.abc import Callable
from typing import Annotated

from fastapi import APIRouter, Depends, Header, HTTPException, status
from fastapi import APIRouter, Body, Depends, Header, HTTPException, status
from httpx import HTTPStatusError
from models_library.api_schemas_storage.storage_schemas import (
LinkType,
Expand Down Expand Up @@ -81,11 +82,10 @@ async def create_program_job(
product_name: Annotated[str, Depends(get_product_name)],
x_simcore_parent_project_uuid: Annotated[ProjectID | None, Header()] = None,
x_simcore_parent_node_id: Annotated[NodeID | None, Header()] = None,
name: Annotated[str | None, Body()] = None,
description: Annotated[str | None, Body()] = None,
):
"""Creates a job in a specific release with given inputs.

NOTE: This operation does **not** start the job
"""
"""Creates a program job"""

# ensures user has access to solver
inputs = JobInputs(values={})
Expand All @@ -97,13 +97,16 @@ async def create_program_job(
)

job, project = await job_service.create_job(
name=name,
description=description,
solver_or_program=program,
inputs=inputs,
parent_project_uuid=x_simcore_parent_project_uuid,
parent_node_id=x_simcore_parent_node_id,
url_for=url_for,
hidden=False,
)

# create workspace directory so files can be uploaded to it
assert len(project.workbench) > 0 # nosec
node_id = next(iter(project.workbench))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,10 @@
from ...services_http.solver_job_models_converters import (
create_jobstatus_from_task,
)
from ...services_rpc.wb_api_server import WbApiRpcClient
from ..dependencies.application import get_reverse_url_mapper
from ..dependencies.authentication import get_current_user_id, get_product_name
from ..dependencies.services import get_api_client
from ..dependencies.webserver_http import AuthSession, get_webserver_session
from ..dependencies.webserver_rpc import (
get_wb_api_rpc_client,
)
from ._constants import (
FMSG_CHANGELOG_ADDED_IN_VERSION,
FMSG_CHANGELOG_CHANGED_IN_VERSION,
Expand Down Expand Up @@ -97,7 +93,6 @@ async def create_solver_job(
user_id: Annotated[PositiveInt, Depends(get_current_user_id)],
solver_service: Annotated[SolverService, Depends()],
job_service: Annotated[JobService, Depends()],
wb_api_rpc: Annotated[WbApiRpcClient, Depends(get_wb_api_rpc_client)],
url_for: Annotated[Callable, Depends(get_reverse_url_mapper)],
product_name: Annotated[str, Depends(get_product_name)],
hidden: Annotated[bool, Query()] = True,
Expand All @@ -116,7 +111,9 @@ async def create_solver_job(
version=version,
product_name=product_name,
)
job, project = await job_service.create_job(
job, _ = await job_service.create_job(
name=None,
description=None,
solver_or_program=solver,
inputs=inputs,
url_for=url_for,
Expand All @@ -125,12 +122,6 @@ async def create_solver_job(
parent_node_id=x_simcore_parent_node_id,
)

await wb_api_rpc.mark_project_as_job(
product_name=product_name,
user_id=user_id,
project_uuid=project.uuid,
job_parent_resource_name=job.runner_name,
)
return job


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
StringConstraints,
TypeAdapter,
ValidationError,
ValidationInfo,
field_validator,
)
from servicelib.logging_utils import LogLevelInt, LogMessageStr
from starlette.datastructures import Headers
Expand Down Expand Up @@ -275,20 +273,13 @@ class Job(BaseModel):
}
)

@field_validator("name", mode="before")
@classmethod
def _check_name(cls, v, info: ValidationInfo):
_id = str(info.data["id"])
if not v.endswith(f"/{_id}"):
msg = f"Resource name [{v}] and id [{_id}] do not match"
raise ValueError(msg)
return v

# constructors ------

@classmethod
def create_now(
cls, parent_name: RelativeResourceName, inputs_checksum: str
cls,
parent_name: RelativeResourceName,
inputs_checksum: str,
) -> "Job":
global_uuid = uuid4()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
class Program(BaseService, ApiServerOutputSchema):
"""A released program with a specific version"""

version_display: str | None

model_config = ConfigDict(
extra="ignore",
json_schema_extra={
Expand All @@ -40,33 +42,50 @@ class Program(BaseService, ApiServerOutputSchema):
"description": "Simulation framework",
"maintainer": "[email protected]",
"url": "https://api.osparc.io/v0/solvers/simcore%2Fservices%2Fdynamic%2Fsim4life/releases/8.0.0",
"version_display": "8.0.0",
}
},
)

@classmethod
def create_from_image(cls, image_meta: ServiceMetaDataPublished) -> "Program":
data = image_meta.model_dump(
include={"name", "key", "version", "description", "contact"},
include={
"name",
"key",
"version",
"description",
"contact",
"version_display",
},
)
return cls(
id=data.pop("key"),
version=data.pop("version"),
title=data.pop("name"),
url=None,
version_display=data.pop("version_display"),
**data,
)

@classmethod
def create_from_service(cls, service: ServiceGetV2) -> "Program":
data = service.model_dump(
include={"name", "key", "version", "description", "contact"},
include={
"name",
"key",
"version",
"description",
"contact",
"version_display",
},
)
return cls(
id=data.pop("key"),
version=data.pop("version"),
title=data.pop("name"),
url=None,
version_display=data.pop("version_display"),
**data,
)

Expand Down
Loading
Loading