Skip to content

Commit

Permalink
Fix/response (#17)
Browse files Browse the repository at this point in the history
* Explicitly set workers

* Remove unused import

* Clean up responses
  • Loading branch information
collindutter committed Apr 23, 2024
1 parent fce4961 commit cd13610
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 43 deletions.
8 changes: 7 additions & 1 deletion griptapecli/commands/skatepark.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ def start(
port: int,
) -> None:
"""Starts the Griptape server."""
uvicorn.run("griptapecli.core.skatepark:app", host=host, port=port, reload=False)
uvicorn.run(
"griptapecli.core.skatepark:app",
host=host,
port=port,
reload=False,
workers=1, # Skatepark only supports 1 worker. We're explictly setting it here to avoid inheriting it from the environment.
)


@skatepark.command(name="register")
Expand Down
14 changes: 13 additions & 1 deletion griptapecli/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Event(BaseModel):
value: dict = Field()


class Run(BaseModel):
class StructureRun(BaseModel):
class Status(Enum):
RUNNING = "RUNNING"
COMPLETED = "COMPLETED"
Expand Down Expand Up @@ -40,3 +40,15 @@ def structure_id(self) -> str:
path = f"{self.directory}/{self.main_file}"

return uuid.uuid5(uuid.NAMESPACE_URL, path).hex


class ListStructuresResponseModel(BaseModel):
structures: list[Structure] = Field(default_factory=lambda: [])


class ListStructureRunsResponseModel(BaseModel):
structure_runs: list[StructureRun] = Field(default_factory=lambda: [])


class ListStructureRunEventsResponseModel(BaseModel):
events: list[Event] = Field(default_factory=lambda: [])
98 changes: 61 additions & 37 deletions griptapecli/core/skatepark.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
import subprocess

from dotenv import dotenv_values
from fastapi import FastAPI, HTTPException

from .models import Event, Run, Structure
from fastapi import FastAPI, HTTPException, status
from .models import (
Event,
StructureRun,
Structure,
ListStructuresResponseModel,
ListStructureRunsResponseModel,
ListStructureRunEventsResponseModel,
)
from .state import State, RunProcess

import asyncio

app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand All @@ -20,7 +24,7 @@
state = State()


@app.post("/api/structures")
@app.post("/api/structures", status_code=status.HTTP_201_CREATED)
def create_structure(structure: Structure) -> Structure:
logger.info(f"Creating structure: {structure}")

Expand All @@ -30,26 +34,30 @@ def create_structure(structure: Structure) -> Structure:
except HTTPException as e:
state.remove_structure(structure.structure_id)

raise HTTPException(status_code=400, detail=str(e))
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))

return structure


@app.get("/api/structures")
def list_structures() -> list[Structure]:
@app.get(
"/api/structures",
response_model=ListStructuresResponseModel,
status_code=status.HTTP_200_OK,
)
def list_structures():
logger.info("Listing structures")

return list(state.structures.values())
return {"structures": list(state.structures.values())}


@app.delete("/api/structures/{structure_id}")
def delete_structure(structure_id: str) -> str:
@app.delete("/api/structures/{structure_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_structure(structure_id: str):
logger.info(f"Deleting structure: {structure_id}")

return state.remove_structure(structure_id)
state.remove_structure(structure_id)


@app.post("/api/structures/{structure_id}/build")
@app.post("/api/structures/{structure_id}/build", status_code=status.HTTP_201_CREATED)
def build_structure(structure_id: str) -> Structure:
logger.info(f"Building structure: {structure_id}")
structure = state.get_structure(structure_id)
Expand All @@ -69,8 +77,8 @@ def build_structure(structure_id: str) -> Structure:
return structure


@app.post("/api/structures/{structure_id}/runs")
def create_structure_run(structure_id: str, run: Run) -> Run:
@app.post("/api/structures/{structure_id}/runs", status_code=status.HTTP_201_CREATED)
def create_structure_run(structure_id: str, run: StructureRun) -> StructureRun:
logger.info(f"Creating run for structure: {structure_id}")
structure = state.get_structure(structure_id)

Expand All @@ -93,28 +101,35 @@ def create_structure_run(structure_id: str, run: Run) -> Run:
return run


@app.get("/api/structures/{structure_id}/runs")
def list_structure_runs(structure_id: str) -> list[Run]:
@app.get(
"/api/structures/{structure_id}/runs",
response_model=ListStructureRunsResponseModel,
status_code=status.HTTP_200_OK,
)
def list_structure_runs(structure_id: str):
logger.info(f"Listing runs for structure: {structure_id}")
return [
run.run
for run in state.runs.values()
if run.run.structure.structure_id == structure_id
]

return {
"structure_runs": [
run.run
for run in state.runs.values()
if run.run.structure.structure_id == structure_id
]
}

@app.patch("/api/structure-runs/{run_id}")
def patch_run(run_id: str, values: dict) -> Run:

@app.patch("/api/structure-runs/{run_id}", status_code=status.HTTP_200_OK)
def patch_run(run_id: str, values: dict) -> StructureRun:
logger.info(f"Patching run: {run_id}")
cur_run = state.runs[run_id].run.model_dump()
new_run = Run(**(cur_run | values))
new_run = StructureRun(**(cur_run | values))
state.runs[run_id].run = new_run

return new_run


@app.get("/api/structure-runs/{run_id}")
def get_run(run_id: str) -> Run:
@app.get("/api/structure-runs/{run_id}", status_code=status.HTTP_200_OK)
def get_run(run_id: str) -> StructureRun:
logger.info(f"Getting run: {run_id}")

run = state.runs[run_id]
Expand All @@ -124,7 +139,7 @@ def get_run(run_id: str) -> Run:
return state.runs[run_id].run


@app.post("/api/structure-runs/{run_id}/events")
@app.post("/api/structure-runs/{run_id}/events", status_code=status.HTTP_201_CREATED)
def create_run_event(run_id: str, event_value: dict) -> Event:
logger.info(f"Creating event for run: {run_id}")
event = Event(value=event_value)
Expand All @@ -138,15 +153,24 @@ def create_run_event(run_id: str, event_value: dict) -> Event:
return event


@app.get("/api/structure-runs/{run_id}/events")
def get_run_events(run_id: str) -> list[Event]:
@app.get(
"/api/structure-runs/{run_id}/events",
status_code=status.HTTP_200_OK,
response_model=ListStructureRunEventsResponseModel,
)
def list_run_events(run_id: str):
logger.info(f"Getting events for run: {run_id}")
return sorted(
state.runs[run_id].run.events, key=lambda event: event.value["timestamp"]
)

events = state.runs[run_id].run.events

sorted_events = sorted(events, key=lambda event: event.value["timestamp"])

return {
"events": sorted_events,
}


def _validate_files(structure: Structure):
def _validate_files(structure: Structure) -> None:
if not os.path.exists(structure.directory):
raise HTTPException(status_code=400, detail="Directory does not exist")

Expand All @@ -171,9 +195,9 @@ def _check_run_process(run_process: RunProcess) -> RunProcess:
if return_code is not None:
stdout, stderr = run_process.process.communicate()
if return_code == 0:
run_process.run.status = Run.Status.COMPLETED
run_process.run.status = StructureRun.Status.COMPLETED
else:
run_process.run.status = Run.Status.FAILED
run_process.run.status = StructureRun.Status.FAILED

run_process.run.stdout = stdout
run_process.run.stderr = stderr
Expand Down
4 changes: 2 additions & 2 deletions griptapecli/core/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from fastapi import HTTPException
from subprocess import Popen

from .models import Run, Structure
from .models import StructureRun, Structure


@define
class RunProcess:
run: Run = field()
run: StructureRun = field()
process: Popen = field()


Expand Down
4 changes: 2 additions & 2 deletions tests/unit/core/test_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from griptapecli.core.models import Event, Run, Structure
from griptapecli.core.models import Event, StructureRun, Structure


class TestModels:
def test_event_model_init(self):
assert Event(value={})

def test_run_model_init(self):
assert Run()
assert StructureRun()

def test_structure_model_init(self):
assert Structure(
Expand Down

0 comments on commit cd13610

Please sign in to comment.