Skip to content

Commit

Permalink
refactor: reorganize and cleanup the experiment restapi endpoint
Browse files Browse the repository at this point in the history
Add docstrings to the refactored experiment service methods.

Closes #312
Co-authored-by: James K. Glasbrenner <[email protected]>
  • Loading branch information
alexb1200 authored Dec 15, 2023
1 parent 8327f17 commit b88f6c6
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 187 deletions.
119 changes: 30 additions & 89 deletions src/dioptra/restapi/experiment/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,19 @@
from __future__ import annotations

import uuid
from typing import List, Optional
from typing import Any, cast

import structlog
from flask import jsonify, request
from flask.wrappers import Response
from flask import request
from flask_accepts import accepts, responds
from flask_login import login_required
from flask_restx import Namespace, Resource
from injector import inject
from structlog.stdlib import BoundLogger

from dioptra.restapi.utils import slugify

from .errors import ExperimentDoesNotExistError
from .model import Experiment
from .schema import ExperimentSchema
from .service import ExperimentService
from .schema import ExperimentSchema, IdStatusResponseSchema, NameStatusResponseSchema
from .service import ExperimentNameService, ExperimentService

LOGGER: BoundLogger = structlog.stdlib.get_logger()

Expand All @@ -55,7 +51,7 @@ def __init__(self, *args, experiment_service: ExperimentService, **kwargs) -> No

@login_required
@responds(schema=ExperimentSchema(many=True), api=api)
def get(self) -> List[Experiment]:
def get(self) -> list[Experiment]:
"""Gets a list of all registered experiments."""
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()), resource="experiment", request_type="GET"
Expand All @@ -71,14 +67,9 @@ def post(self) -> Experiment:
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()), resource="experiment", request_type="POST"
) # noqa: F841

log.info("Request received")

parsed_obj = request.parsed_obj # type: ignore

name = slugify(str(parsed_obj["name"]))

return self._experiment_service.create(experiment_name=name, log=log)
return self._experiment_service.create(parsed_obj["name"], log=log)


@api.route("/<int:experimentId>")
Expand All @@ -99,28 +90,22 @@ def get(self, experimentId: int) -> Experiment:
request_id=str(uuid.uuid4()), resource="experimentId", request_type="GET"
) # noqa: F841
log.info("Request received", experiment_id=experimentId)
experiment: Optional[Experiment] = self._experiment_service.get_by_id(
experiment_id=experimentId, log=log
return cast(
Experiment,
self._experiment_service.get(
experimentId, error_if_not_found=True, log=log
),
)

if experiment is None:
log.error("Experiment not found", experiment_id=experimentId)
raise ExperimentDoesNotExistError

return experiment

@login_required
def delete(self, experimentId: int) -> Response:
@responds(schema=IdStatusResponseSchema)
def delete(self, experimentId: int) -> dict[str, Any]:
"""Deletes an experiment by its unique identifier."""
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()), resource="experimentId", request_type="DELETE"
) # noqa: F841
log.info("Request received", experiment_id=experimentId)
id: List[int] = self._experiment_service.delete_experiment(
experiment_id=experimentId
)

return jsonify(dict(status="Success", id=id))
return self._experiment_service.delete(experimentId)

@login_required
@accepts(schema=ExperimentSchema, api=api)
Expand All @@ -130,30 +115,22 @@ def put(self, experimentId: int) -> Experiment:
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()), resource="experimentId", request_type="PUT"
) # noqa: F841
changes: dict = request.parsed_obj # type: ignore
experiment: Optional[Experiment] = self._experiment_service.get_by_id(
experimentId, log=log
)

if experiment is None:
log.error("Experiment not found", experiment_id=experimentId)
raise ExperimentDoesNotExistError

experiment = self._experiment_service.rename_experiment(
experiment=experiment, new_name=changes["name"], log=log
parsed_obj = request.parsed_obj # type: ignore
return self._experiment_service.rename(
experimentId, new_name=parsed_obj["name"], log=log
)

return experiment


@api.route("/name/<string:experimentName>")
@api.param("experimentName", "The name of the experiment.")
class ExperimentNameResource(Resource):
"""Shows a single experiment (name reference) and lets you modify and delete it."""
"""Shows a single experiment (name reference) and delete it."""

@inject
def __init__(self, *args, experiment_service: ExperimentService, **kwargs) -> None:
self._experiment_service = experiment_service
def __init__(
self, *args, experiment_name_service: ExperimentNameService, **kwargs
) -> None:
self._experiment_name_service = experiment_name_service
super().__init__(*args, **kwargs)

@login_required
Expand All @@ -164,18 +141,16 @@ def get(self, experimentName: str) -> Experiment:
request_id=str(uuid.uuid4()), resource="experimentName", request_type="GET"
) # noqa: F841
log.info("Request received", experiment_name=experimentName)
experiment: Optional[Experiment] = self._experiment_service.get_by_name(
experiment_name=experimentName, log=log
return cast(
Experiment,
self._experiment_name_service.get(
experimentName, error_if_not_found=True, log=log
),
)

if experiment is None:
log.error("Experiment not found", experiment_name=experimentName)
raise ExperimentDoesNotExistError

return experiment

@login_required
def delete(self, experimentName: str) -> Response:
@responds(schema=NameStatusResponseSchema)
def delete(self, experimentName: str) -> dict[str, Any]:
"""Deletes an experiment by its unique name."""
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()),
Expand All @@ -184,38 +159,4 @@ def delete(self, experimentName: str) -> Response:
request_type="DELETE",
) # noqa: F841
log.info("Request received")
experiment: Optional[Experiment] = self._experiment_service.get_by_name(
experiment_name=experimentName, log=log
)

if experiment is None:
return jsonify(dict(status="Success", id=[]))

id: List[int] = self._experiment_service.delete_experiment(
experiment_id=experiment.experiment_id
)

return jsonify(dict(status="Success", id=id))

@login_required
@accepts(schema=ExperimentSchema, api=api)
@responds(schema=ExperimentSchema, api=api)
def put(self, experimentName: str) -> Experiment:
"""Modifies an experiment by its unique name."""
log: BoundLogger = LOGGER.new(
request_id=str(uuid.uuid4()), resource="experimentName", request_type="PUT"
) # noqa: F841
changes: dict = request.parsed_obj # type: ignore
experiment: Optional[Experiment] = self._experiment_service.get_by_name(
experiment_name=experimentName, log=log
)

if experiment is None:
log.error("Experiment not found", experiment_name=experimentName)
raise ExperimentDoesNotExistError

experiment = self._experiment_service.rename_experiment(
experiment=experiment, new_name=changes["name"], log=log
)

return experiment
return self._experiment_name_service.delete(experimentName, log=log)
44 changes: 33 additions & 11 deletions src/dioptra/restapi/experiment/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,14 @@
"""The schemas for serializing/deserializing the experiment endpoint objects.
.. |Experiment| replace:: :py:class:`~.model.Experiment`
.. |ExperimentRegistrationForm| replace:: :py:class:`~.model.ExperimentRegistrationForm`
.. |ExperimentRegistrationFormData| replace:: \
:py:class:`~.model.ExperimentRegistrationFormData`
"""
from __future__ import annotations

from marshmallow import Schema, fields


class ExperimentSchema(Schema):
"""The schema for the data stored in an |Experiment| object.
Attributes:
experimentId: An integer identifying a registered experiment.
createdOn: The date and time the experiment was created.
lastModified: The date and time the experiment was last modified.
name: The name of the experiment.
"""
"""The schema for the data stored in an |Experiment| object."""

experimentId = fields.Integer(
attribute="experiment_id",
Expand All @@ -56,3 +46,35 @@ class ExperimentSchema(Schema):
name = fields.String(
attribute="name", metadata=dict(description="The name of the experiment.")
)


class IdStatusResponseSchema(Schema):
"""A simple response for reporting a status for one or more objects."""

status = fields.String(
attribute="status",
metadata=dict(description="The status of the request."),
)
id = fields.List(
fields.Integer(),
attribute="id",
metadata=dict(
description="A list of integers identifying the affected object(s)."
),
)


class NameStatusResponseSchema(Schema):
"""A simple response for reporting a status for one or more objects."""

status = fields.String(
attribute="status",
metadata=dict(description="The status of the request."),
)
name = fields.List(
fields.String(),
attribute="name",
metadata=dict(
description="A list of names identifying the affected object(s)."
),
)
Loading

0 comments on commit b88f6c6

Please sign in to comment.