Skip to content

Commit

Permalink
Merge pull request #83 from i-VRESSE/rename
Browse files Browse the repository at this point in the history
Add POST /job/{jobid}/name route
  • Loading branch information
sverhoeven authored Feb 1, 2024
2 parents 92314c7 + 3a24869 commit 1552508
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 6 deletions.
17 changes: 17 additions & 0 deletions src/bartender/db/dao/job_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,22 @@ async def update_internal_job_id(
job.destination = destination
await self.session.commit()

async def set_job_name(self, jobid: int, user: str, name: str) -> None:
"""Set name of a job.
Args:
jobid: name of job instance.
user: Which user to get jobs from.
name: new name of job instance.
Raises:
IndexError: if job was not found or user is not the owner.
"""
job = await self.session.get(Job, jobid)
if job is None or job.submitter != user:
raise IndexError("Job not found")
job.name = name
await self.session.commit()


CurrentJobDAO = Annotated[JobDAO, Depends()]
10 changes: 6 additions & 4 deletions src/bartender/db/models/job_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,18 @@

CompletedStates: set[State] = {"ok", "error"}

MAX_LENGTH_NAME = 200


class Job(Base):
"""Model for the Job."""

__tablename__ = "job"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(length=200)) # noqa: WPS432
name: Mapped[str] = mapped_column(String(length=MAX_LENGTH_NAME))
application: Mapped[str] = mapped_column(
String(length=200), # noqa: WPS432
String(length=MAX_LENGTH_NAME),
)
state: Mapped[State] = mapped_column(
String(length=20), # noqa: WPS432
Expand All @@ -50,10 +52,10 @@ class Job(Base):
submitter: Mapped[str] = mapped_column(String(length=254)) # noqa: WPS432
# Identifier for job used by the scheduler
internal_id: Mapped[Optional[str]] = mapped_column(
String(length=200), # noqa: WPS432
String(length=MAX_LENGTH_NAME),
)
destination: Mapped[Optional[str]] = mapped_column(
String(length=200), # noqa: WPS432
String(length=MAX_LENGTH_NAME),
)
created_on: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
Expand Down
39 changes: 37 additions & 2 deletions src/bartender/web/api/job/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
from pathlib import Path
from typing import Annotated, Literal, Optional, Tuple, Type, Union

from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request
from fastapi import (
APIRouter,
BackgroundTasks,
Body,
Depends,
HTTPException,
Query,
Request,
)
from fastapi.responses import FileResponse, PlainTextResponse
from fs.copy import copy_fs
from fs.osfs import OSFS
Expand All @@ -18,7 +26,7 @@
from bartender.config import CurrentConfig, InteractiveApplicationConfiguration
from bartender.context import CurrentContext, get_job_root_dir
from bartender.db.dao.job_dao import CurrentJobDAO
from bartender.db.models.job_model import CompletedStates, Job
from bartender.db.models.job_model import MAX_LENGTH_NAME, CompletedStates, Job
from bartender.filesystem.walk_dir import DirectoryItem, walk_dir
from bartender.filesystems.queue import CurrentFileOutStagingQueue
from bartender.web.api.job.interactive_apps import InteractiveAppResult, run
Expand Down Expand Up @@ -518,3 +526,30 @@ async def run_interactive_app(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=exc.message,
) from exc


@router.post("/{jobid}/name")
async def rename_job_name(
jobid: int,
job_dao: CurrentJobDAO,
user: CurrentUser,
name: Annotated[str, Body(max_length=MAX_LENGTH_NAME, min_length=1)],
) -> None:
"""Rename the name of a job.
Args:
jobid: The job identifier.
job_dao: The job DAO.
user: The current user.
name: The new name of the job.
Raises:
HTTPException: When job is not found. Or when user is not owner of job.
"""
try:
await job_dao.set_job_name(jobid, user.username, name)
except IndexError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Job not found",
) from exc
69 changes: 69 additions & 0 deletions tests/web/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ async def test_retrieve_job_new(
assert job == expected


@pytest.mark.anyio
async def test_retrieve_job_unknown(
fastapi_app: FastAPI,
client: AsyncClient,
Expand All @@ -192,6 +193,7 @@ async def test_retrieve_job_stdout_unknown(
assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.anyio
async def test_retrieve_job_queued2running(
dbsession: AsyncSession,
current_user: User,
Expand Down Expand Up @@ -219,6 +221,7 @@ async def test_retrieve_job_queued2running(
download_mock.assert_not_called()


@pytest.mark.anyio
async def test_retrieve_job_completed(
dbsession: AsyncSession,
current_user: User,
Expand Down Expand Up @@ -246,6 +249,7 @@ async def test_retrieve_job_completed(
download_mock.assert_not_called()


@pytest.mark.anyio
async def test_retrieve_job_running2ok(
dbsession: AsyncSession,
current_user: User,
Expand Down Expand Up @@ -287,6 +291,7 @@ async def test_retrieve_job_running2ok(
download_mock.assert_called_once()


@pytest.mark.anyio
async def test_retrieve_jobs_queued2running(
dbsession: AsyncSession,
current_user: User,
Expand Down Expand Up @@ -316,6 +321,7 @@ async def test_retrieve_jobs_queued2running(
download_mock.assert_not_called()


@pytest.mark.anyio
async def test_retrieve_jobs_running2staging_out(
dbsession: AsyncSession,
current_user: User,
Expand Down Expand Up @@ -764,3 +770,66 @@ async def test_run_interactive_app_invalid_requestbody(
assert response.json() == {
"detail": "Additional properties are not allowed ('foo' was unexpected)",
}


@pytest.mark.anyio
async def test_rename_job_name(
fastapi_app: FastAPI,
client: AsyncClient,
auth_headers: Dict[str, str],
mock_ok_job: int,
) -> None:
url = fastapi_app.url_path_for("rename_job_name", jobid=str(mock_ok_job))
response = await client.post(url, headers=auth_headers, json="newname")

assert response.status_code == status.HTTP_200_OK

url = fastapi_app.url_path_for("retrieve_job", jobid=str(mock_ok_job))
response2 = await client.get(url, headers=auth_headers)
assert response2.status_code == status.HTTP_200_OK

renamed_job = response2.json()
assert renamed_job["name"] == "newname"


@pytest.mark.anyio
async def test_rename_job_name_too_short(
fastapi_app: FastAPI,
client: AsyncClient,
auth_headers: Dict[str, str],
mock_ok_job: int,
) -> None:
jobid = str(mock_ok_job)
name = ""
url = fastapi_app.url_path_for("rename_job_name", jobid=jobid)
response = await client.post(url, headers=auth_headers, json=name)

assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
expected = {
"detail": [
{
"ctx": {"limit_value": 1},
"loc": ["body"],
"msg": "ensure this value has at least 1 characters",
"type": "value_error.any_str.min_length",
},
],
}
assert response.json() == expected


@pytest.mark.anyio
async def test_rename_job_name_wrong_user(
fastapi_app: FastAPI,
client: AsyncClient,
second_user_token: str,
mock_ok_job: int,
) -> None:
jobid = str(mock_ok_job)
name = "newname"
url = fastapi_app.url_path_for("rename_job_name", jobid=jobid)
headers = {"Authorization": f"Bearer {second_user_token}"}
response = await client.post(url, headers=headers, json=name)

assert response.status_code == status.HTTP_404_NOT_FOUND
assert response.json() == {"detail": "Job not found"}

0 comments on commit 1552508

Please sign in to comment.