Skip to content

Commit 884cac2

Browse files
committed
start moving out url_for
1 parent 7e9634a commit 884cac2

File tree

10 files changed

+143
-69
lines changed

10 files changed

+143
-69
lines changed

services/api-server/src/simcore_service_api_server/_service_function_jobs.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@
3636
from pydantic import ValidationError
3737

3838
from ._service_jobs import JobService
39-
from .models.schemas.jobs import JobInputs, JobPricingSpecification
39+
from .models.schemas.jobs import (
40+
JobInputs,
41+
JobPricingSpecification,
42+
get_solver_job_rest_interface_links,
43+
)
4044
from .services_rpc.wb_api_server import WbApiRpcClient
4145

4246

@@ -244,11 +248,16 @@ async def run_function(
244248
)
245249

246250
if function.function_class == FunctionClass.SOLVER:
251+
job_rest_interface_links = get_solver_job_rest_interface_links(
252+
url_for=url_for,
253+
solver_key=function.solver_key,
254+
version=function.solver_version,
255+
)
247256
solver_job = await self._job_service.create_solver_job(
248257
solver_key=function.solver_key,
249258
version=function.solver_version,
250259
inputs=JobInputs(values=joined_inputs or {}),
251-
url_for=url_for,
260+
job_rest_interface_links=job_rest_interface_links,
252261
hidden=True,
253262
x_simcore_parent_project_uuid=x_simcore_parent_project_uuid,
254263
x_simcore_parent_node_id=x_simcore_parent_node_id,

services/api-server/src/simcore_service_api_server/_service_jobs.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from collections.abc import Callable
32
from dataclasses import dataclass
43
from pathlib import Path
54
from uuid import UUID
@@ -24,15 +23,18 @@
2423
from models_library.rpc.webserver.projects import ProjectJobRpcGet
2524
from models_library.rpc_pagination import PageLimitInt
2625
from models_library.users import UserID
27-
from pydantic import HttpUrl
2826
from servicelib.logging_utils import log_context
2927

3028
from ._service_solvers import (
3129
SolverService,
3230
)
3331
from .exceptions.backend_errors import JobAssetsMissingError
3432
from .exceptions.custom_errors import SolverServiceListJobsFiltersError
35-
from .models.api_resources import RelativeResourceName, compose_resource_name
33+
from .models.api_resources import (
34+
JobRestInterfaceLinks,
35+
RelativeResourceName,
36+
compose_resource_name,
37+
)
3638
from .models.basic_types import NameValueTuple, VersionStr
3739
from .models.schemas.jobs import (
3840
Job,
@@ -211,7 +213,7 @@ async def create_project_marked_as_job(
211213
inputs: JobInputs,
212214
parent_project_uuid: ProjectID | None,
213215
parent_node_id: NodeID | None,
214-
url_for: Callable[..., HttpUrl],
216+
job_rest_interface_links: JobRestInterfaceLinks,
215217
hidden: bool,
216218
project_name: str | None,
217219
description: str | None,
@@ -252,7 +254,9 @@ async def create_project_marked_as_job(
252254

253255
# for consistency, it rebuild job
254256
job = create_job_from_project(
255-
solver_or_program=solver_or_program, project=new_project, url_for=url_for
257+
solver_or_program=solver_or_program,
258+
project=new_project,
259+
job_rest_interface_links=job_rest_interface_links,
256260
)
257261
assert job.id == pre_job.id # nosec
258262
assert job.name == pre_job.name # nosec
@@ -311,8 +315,8 @@ async def create_solver_job(
311315
solver_key: SolverKeyId,
312316
version: VersionStr,
313317
inputs: JobInputs,
314-
url_for: Callable,
315318
hidden: bool,
319+
job_rest_interface_links: JobRestInterfaceLinks,
316320
x_simcore_parent_project_uuid: ProjectID | None,
317321
x_simcore_parent_node_id: NodeID | None,
318322
) -> Job:
@@ -326,10 +330,10 @@ async def create_solver_job(
326330
description=None,
327331
solver_or_program=solver,
328332
inputs=inputs,
329-
url_for=url_for,
330333
hidden=hidden,
331334
parent_project_uuid=x_simcore_parent_project_uuid,
332335
parent_node_id=x_simcore_parent_node_id,
336+
job_rest_interface_links=job_rest_interface_links,
333337
)
334338

335339
return job

services/api-server/src/simcore_service_api_server/api/routes/programs.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
complete_file_upload,
1717
get_upload_links_from_s3,
1818
)
19+
from simcore_service_api_server.models.api_resources import JobRestInterfaceLinks
1920

2021
from ..._service_jobs import JobService
2122
from ..._service_programs import ProgramService
@@ -171,6 +172,11 @@ async def create_program_job(
171172
name=program_key,
172173
version=version,
173174
)
175+
job_rest_interface_links = JobRestInterfaceLinks(
176+
url_template=None,
177+
runner_url_template=None,
178+
outputs_url_template=None,
179+
)
174180

175181
job, project = await job_service.create_project_marked_as_job(
176182
project_name=name,
@@ -179,7 +185,7 @@ async def create_program_job(
179185
inputs=inputs,
180186
parent_project_uuid=x_simcore_parent_project_uuid,
181187
parent_node_id=x_simcore_parent_node_id,
182-
url_for=url_for,
188+
job_rest_interface_links=job_rest_interface_links,
183189
hidden=False,
184190
)
185191

services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
JobMetadataUpdate,
2525
JobPricingSpecification,
2626
JobStatus,
27+
get_solver_job_rest_interface_links,
2728
)
2829
from ...models.schemas.solvers import Solver, SolverKeyId
2930
from ...services_http.director_v2 import DirectorV2Api
@@ -103,7 +104,9 @@ async def create_solver_job( # noqa: PLR0913
103104
hidden=hidden,
104105
x_simcore_parent_project_uuid=x_simcore_parent_project_uuid,
105106
x_simcore_parent_node_id=x_simcore_parent_node_id,
106-
url_for=url_for,
107+
job_rest_interface_links=get_solver_job_rest_interface_links(
108+
url_for=url_for, solver_key=solver_key, version=version
109+
),
107110
)
108111

109112

services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_read.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
JobLog,
3939
JobMetadata,
4040
JobOutputs,
41+
get_solver_job_rest_interface_links,
4142
)
4243
from ...models.schemas.jobs_filters import JobMetadataFilter
4344
from ...models.schemas.model_adapter import (
@@ -203,9 +204,14 @@ async def list_jobs(
203204
)
204205

205206
jobs: deque[Job] = deque()
207+
job_rest_interface_links = get_solver_job_rest_interface_links(
208+
url_for=url_for, solver_key=solver_key, version=solver.version
209+
)
206210
for prj in projects_page.data:
207211
job = create_job_from_project(
208-
solver_or_program=solver, project=prj, url_for=url_for
212+
solver_or_program=solver,
213+
project=prj,
214+
job_rest_interface_links=job_rest_interface_links,
209215
)
210216
assert job.id == prj.uuid # nosec
211217
assert job.name == prj.name # nosec
@@ -247,9 +253,16 @@ async def list_jobs_paginated(
247253
projects_page = await webserver_api.get_projects_w_solver_page(
248254
solver_name=solver.name, limit=page_params.limit, offset=page_params.offset
249255
)
256+
job_rest_interface_links = get_solver_job_rest_interface_links(
257+
url_for=url_for, solver_key=solver_key, version=version
258+
)
250259

251260
jobs: list[Job] = [
252-
create_job_from_project(solver_or_program=solver, project=prj, url_for=url_for)
261+
create_job_from_project(
262+
solver_or_program=solver,
263+
project=prj,
264+
job_rest_interface_links=job_rest_interface_links,
265+
)
253266
for prj in projects_page.data
254267
]
255268

@@ -285,8 +298,14 @@ async def get_job(
285298
)
286299
project: ProjectGet = await webserver_api.get_project(project_id=job_id)
287300

301+
job_rest_interface_links = get_solver_job_rest_interface_links(
302+
url_for=url_for, solver_key=solver_key, version=version
303+
)
304+
288305
job = create_job_from_project(
289-
solver_or_program=solver, project=project, url_for=url_for
306+
solver_or_program=solver,
307+
project=project,
308+
job_rest_interface_links=job_rest_interface_links,
290309
)
291310
assert job.id == job_id # nosec
292311
return job # nosec

services/api-server/src/simcore_service_api_server/models/api_resources.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import re
22
import urllib.parse
33
from typing import Annotated, TypeAlias
4+
from uuid import UUID
45

5-
from pydantic import Field, TypeAdapter
6+
import parse
7+
from pydantic import AfterValidator, BaseModel, Field, HttpUrl, TypeAdapter
68
from pydantic.types import StringConstraints
79

810
# RESOURCE NAMES https://google.aip.dev/122
@@ -26,7 +28,6 @@
2628
# SEE https://tools.ietf.org/html/rfc3986#appendix-B
2729
#
2830

29-
3031
_RELATIVE_RESOURCE_NAME_RE = r"^([^\s/]+/?){1,10}$"
3132

3233

@@ -91,3 +92,39 @@ def split_resource_name_as_dict(
9192
"""
9293
parts = split_resource_name(resource_name)
9394
return dict(zip(parts[::2], parts[1::2], strict=False))
95+
96+
97+
def _url_missing_only_job_id(url: str | None) -> str | None:
98+
if url is None:
99+
return None
100+
if set(parse.compile(url).named_fields) != {"job_id"}:
101+
raise ValueError(f"Missing job_id in {url=}")
102+
return url
103+
104+
105+
class JobRestInterfaceLinks(BaseModel):
106+
url_template: Annotated[str | None, AfterValidator(_url_missing_only_job_id)]
107+
runner_url_template: str | None
108+
outputs_url_template: Annotated[
109+
str | None, AfterValidator(_url_missing_only_job_id)
110+
]
111+
112+
def url(self, job_id: UUID) -> HttpUrl | None:
113+
if self.url_template is None:
114+
return None
115+
return TypeAdapter(HttpUrl).validate_python(
116+
self.url_template.format(job_id=job_id)
117+
)
118+
119+
def runner_url(self, job_id: UUID) -> HttpUrl | None:
120+
assert job_id # nosec
121+
if self.runner_url_template is None:
122+
return None
123+
return TypeAdapter(HttpUrl).validate_python(self.runner_url_template)
124+
125+
def outputs_url(self, job_id: UUID) -> HttpUrl | None:
126+
if self.outputs_url_template is None:
127+
return None
128+
return TypeAdapter(HttpUrl).validate_python(
129+
self.outputs_url_template.format(job_id=job_id)
130+
)

services/api-server/src/simcore_service_api_server/models/schemas/jobs.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from servicelib.logging_utils import LogLevelInt, LogMessageStr
2929
from starlette.datastructures import Headers
3030

31+
from ...models.api_resources import JobRestInterfaceLinks
3132
from ...models.schemas.files import File, UserFile
3233
from .._utils_pydantic import UriSchema
3334
from ..api_resources import (
@@ -47,8 +48,8 @@
4748
# - Input/outputs are defined in service metadata
4849
# - custom metadata
4950
#
50-
from .programs import Program, ProgramKeyId
51-
from .solvers import Solver
51+
from .programs import ProgramKeyId
52+
from .solvers import SolverKeyId
5253

5354
JobID: TypeAlias = UUID
5455

@@ -324,42 +325,28 @@ def resource_name(self) -> str:
324325
return self.name
325326

326327

327-
def get_url(
328-
solver_or_program: Solver | Program, url_for: Callable[..., HttpUrl], job_id: JobID
329-
) -> HttpUrl | None:
330-
if isinstance(solver_or_program, Solver):
331-
return url_for(
328+
def get_solver_job_rest_interface_links(
329+
*, url_for: Callable, solver_key: SolverKeyId, version: VersionStr
330+
) -> JobRestInterfaceLinks:
331+
return JobRestInterfaceLinks(
332+
url_template=url_for(
332333
"get_job",
333-
solver_key=solver_or_program.id,
334-
version=solver_or_program.version,
335-
job_id=job_id,
336-
)
337-
return None
338-
339-
340-
def get_runner_url(
341-
solver_or_program: Solver | Program, url_for: Callable[..., HttpUrl]
342-
) -> HttpUrl | None:
343-
if isinstance(solver_or_program, Solver):
344-
return url_for(
334+
solver_key=solver_key,
335+
version=version,
336+
job_id="{job_id}",
337+
),
338+
runner_url_template=url_for(
345339
"get_solver_release",
346-
solver_key=solver_or_program.id,
347-
version=solver_or_program.version,
348-
)
349-
return None
350-
351-
352-
def get_outputs_url(
353-
solver_or_program: Solver | Program, url_for: Callable[..., HttpUrl], job_id: JobID
354-
) -> HttpUrl | None:
355-
if isinstance(solver_or_program, Solver):
356-
return url_for(
340+
solver_key=solver_key,
341+
version=version,
342+
),
343+
outputs_url_template=url_for(
357344
"get_job_outputs",
358-
solver_key=solver_or_program.id,
359-
version=solver_or_program.version,
360-
job_id=job_id,
361-
)
362-
return None
345+
solver_key=solver_key,
346+
version=version,
347+
job_id="{job_id}",
348+
),
349+
)
363350

364351

365352
PercentageInt: TypeAlias = Annotated[int, Field(ge=0, le=100)]

0 commit comments

Comments
 (0)