selfprivacy-rest-api/selfprivacy_api/jobs/__init__.py

225 lines
5.8 KiB
Python
Raw Normal View History

2022-07-30 15:24:21 +00:00
"""
Jobs controller. It handles the jobs that are created by the user.
This is a singleton class holding the jobs list.
Jobs can be added and removed.
A single job can be updated.
A job is a dictionary with the following keys:
- id: unique identifier of the job
- name: name of the job
- description: description of the job
- status: status of the job
- created_at: date of creation of the job
- updated_at: date of last update of the job
- finished_at: date of finish of the job
- error: error message if the job failed
- result: result of the job
"""
import typing
import datetime
from uuid import UUID
2022-07-30 15:24:21 +00:00
import uuid
from enum import Enum
from pydantic import BaseModel
2022-11-16 13:54:54 +00:00
from selfprivacy_api.utils.redis_pool import RedisPool
2022-07-30 15:24:21 +00:00
class JobStatus(Enum):
"""
Status of a job.
"""
CREATED = "CREATED"
RUNNING = "RUNNING"
FINISHED = "FINISHED"
ERROR = "ERROR"
class Job(BaseModel):
2022-07-30 15:24:21 +00:00
"""
Job class.
"""
2022-09-09 14:42:40 +00:00
uid: UUID
type_id: str
name: str
description: str
status: JobStatus
status_text: typing.Optional[str]
progress: typing.Optional[int]
created_at: datetime.datetime
updated_at: datetime.datetime
finished_at: typing.Optional[datetime.datetime]
error: typing.Optional[str]
result: typing.Optional[str]
2022-07-30 15:24:21 +00:00
class Jobs:
"""
Jobs class.
"""
@staticmethod
def reset() -> None:
"""
Reset the jobs list.
"""
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
jobs = Jobs.get_jobs()
for job in jobs:
r.delete(redis_key_from_uuid(job.uid))
r.delete("jobs")
@staticmethod
2022-07-30 15:24:21 +00:00
def add(
name: str,
type_id: str,
description: str,
status: JobStatus = JobStatus.CREATED,
status_text: str = "",
progress: int = 0,
2022-07-30 15:24:21 +00:00
) -> Job:
"""
Add a job to the jobs list.
"""
job = Job(
2022-09-09 14:42:40 +00:00
uid=uuid.uuid4(),
2022-07-30 15:24:21 +00:00
name=name,
type_id=type_id,
2022-07-30 15:24:21 +00:00
description=description,
status=status,
status_text=status_text,
progress=progress,
2022-07-30 15:24:21 +00:00
created_at=datetime.datetime.now(),
updated_at=datetime.datetime.now(),
finished_at=None,
error=None,
result=None,
)
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
store_job_as_hash(r, redis_key_from_uuid(job.uid), job)
r.lpush("jobs", redis_key_from_uuid(job.uid))
2022-07-30 15:24:21 +00:00
return job
@staticmethod
def remove(job: Job) -> None:
2022-07-30 15:24:21 +00:00
"""
Remove a job from the jobs list.
"""
Jobs.remove_by_uid(str(job.uid))
@staticmethod
def remove_by_uid(job_uuid: str) -> bool:
"""
Remove a job from the jobs list.
"""
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
key = redis_key_from_uuid(job_uuid)
r.delete(key)
r.lrem("jobs", 0, key)
return False
2022-07-30 15:24:21 +00:00
@staticmethod
2022-07-30 15:24:21 +00:00
def update(
job: Job,
status: JobStatus,
status_text: typing.Optional[str] = None,
progress: typing.Optional[int] = None,
name: typing.Optional[str] = None,
description: typing.Optional[str] = None,
error: typing.Optional[str] = None,
result: typing.Optional[str] = None,
2022-07-30 15:24:21 +00:00
) -> Job:
"""
Update a job in the jobs list.
"""
if name is not None:
job.name = name
if description is not None:
job.description = description
if status_text is not None:
job.status_text = status_text
if progress is not None:
job.progress = progress
2022-07-30 15:24:21 +00:00
job.status = status
job.updated_at = datetime.datetime.now()
job.error = error
job.result = result
if status in (JobStatus.FINISHED, JobStatus.ERROR):
job.finished_at = datetime.datetime.now()
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
key = redis_key_from_uuid(job.uid)
if exists_sync(r, key):
store_job_as_hash(r, key, job)
2022-07-30 15:24:21 +00:00
return job
@staticmethod
def get_job(uid: str) -> typing.Optional[Job]:
2022-07-30 15:24:21 +00:00
"""
Get a job from the jobs list.
"""
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
key = redis_key_from_uuid(uid)
if exists_sync(r, key):
return job_from_hash(r, key)
2022-07-30 15:24:21 +00:00
return None
@staticmethod
def get_jobs() -> typing.List[Job]:
2022-07-30 15:24:21 +00:00
"""
Get the jobs list.
"""
2022-11-16 13:54:54 +00:00
r = RedisPool().get_connection()
jobs = r.lrange("jobs", 0, -1)
2022-11-16 13:54:54 +00:00
return [job_from_hash(r, job_key) for job_key in jobs]
@staticmethod
def is_busy() -> bool:
"""
Check if there is a job running.
"""
2022-11-16 13:54:54 +00:00
for job in Jobs.get_jobs():
if job["status"] == JobStatus.RUNNING.value:
return True
return False
2022-11-16 13:54:54 +00:00
def redis_key_from_uuid(uuid):
return "jobs:" + str(uuid)
def store_job_as_hash(r, redis_key, model):
for key, value in model.dict().items():
if isinstance(value, uuid.UUID):
value = str(value)
if isinstance(value, datetime.datetime):
value = value.isoformat()
if isinstance(value, JobStatus):
value = value.value
r.hset(redis_key, key, str(value))
2022-11-16 13:54:54 +00:00
def job_from_hash(r, redis_key):
if exists_sync(r, redis_key):
job_dict = r.hgetall(redis_key)
2022-11-16 13:54:54 +00:00
for date in [
"created_at",
"updated_at",
"finished_at",
]:
if job_dict[date] != "None":
job_dict[date] = datetime.datetime.fromisoformat(job_dict[date])
for key in job_dict.keys():
if job_dict[key] == "None":
job_dict[key] = None
return Job(**job_dict)
return None
def exists_sync(r, key):
return r.exists(key)