Skip to content

Commit

Permalink
Merge pull request #158 from lsst-dm/tickets/DM-47995/refactor_routes
Browse files Browse the repository at this point in the history
feat(api): Refactor api routes
  • Loading branch information
tcjennings authored Jan 14, 2025
2 parents 016065a + f747c62 commit f1cc697
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 234 deletions.
2 changes: 1 addition & 1 deletion src/lsst/cmservice/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class AsgiConfiguration(BaseModel):

prefix: str = Field(
description="The URL prefix for the cm-service API",
default="/cm-service/v1",
default="/cmservice",
)

frontend_prefix: str = Field(
Expand Down
57 changes: 7 additions & 50 deletions src/lsst/cmservice/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,9 @@
from . import __version__
from .config import config
from .routers import (
actions,
campaigns,
groups,
healthz,
index,
jobs,
loaders,
pipetask_error_types,
pipetask_errors,
product_sets,
productions,
queues,
script_dependencies,
script_errors,
script_templates,
scripts,
spec_blocks,
specifications,
step_dependencies,
steps,
task_sets,
wms_task_reports,
v1,
)
from .web_app import web_app

Expand Down Expand Up @@ -137,41 +118,17 @@ async def lifespan(app: FastAPI) -> AsyncGenerator:
lifespan=lifespan,
title=config.asgi.title,
version=__version__,
openapi_url=f"{config.asgi.prefix}/openapi.json",
openapi_url="/openapi.json",
openapi_tags=tags_metadata,
docs_url=f"{config.asgi.prefix}/docs",
redoc_url=f"{config.asgi.prefix}/redoc",
docs_url="/docs",
redoc_url="/redoc",
)

app.add_middleware(XForwardedMiddleware)

app.include_router(healthz.health_router)
app.include_router(index.router)
app.include_router(loaders.router, prefix=config.asgi.prefix)
app.include_router(actions.router, prefix=config.asgi.prefix)

app.include_router(productions.router, prefix=config.asgi.prefix)
app.include_router(campaigns.router, prefix=config.asgi.prefix)
app.include_router(steps.router, prefix=config.asgi.prefix)
app.include_router(groups.router, prefix=config.asgi.prefix)
app.include_router(jobs.router, prefix=config.asgi.prefix)
app.include_router(scripts.router, prefix=config.asgi.prefix)

app.include_router(specifications.router, prefix=config.asgi.prefix)
app.include_router(spec_blocks.router, prefix=config.asgi.prefix)
app.include_router(script_templates.router, prefix=config.asgi.prefix)

app.include_router(pipetask_error_types.router, prefix=config.asgi.prefix)
app.include_router(pipetask_errors.router, prefix=config.asgi.prefix)
app.include_router(script_errors.router, prefix=config.asgi.prefix)

app.include_router(task_sets.router, prefix=config.asgi.prefix)
app.include_router(product_sets.router, prefix=config.asgi.prefix)
app.include_router(wms_task_reports.router, prefix=config.asgi.prefix)

app.include_router(script_dependencies.router, prefix=config.asgi.prefix)
app.include_router(step_dependencies.router, prefix=config.asgi.prefix)
app.include_router(queues.router, prefix=config.asgi.prefix)
app.include_router(healthz.health_router, prefix="")
app.include_router(index.router, prefix="")
app.include_router(v1.router, prefix=config.asgi.prefix)

# Start the frontend web application.
app.mount(config.asgi.frontend_prefix, web_app)
Expand Down
54 changes: 54 additions & 0 deletions src/lsst/cmservice/routers/v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from fastapi import APIRouter

from . import (
actions,
campaigns,
groups,
jobs,
loaders,
pipetask_error_types,
pipetask_errors,
product_sets,
productions,
queues,
script_dependencies,
script_errors,
script_templates,
scripts,
spec_blocks,
specifications,
step_dependencies,
steps,
task_sets,
wms_task_reports,
)

router = APIRouter(
prefix="/v1",
)

router.include_router(loaders.router)
router.include_router(actions.router)

router.include_router(productions.router)
router.include_router(campaigns.router)
router.include_router(steps.router)
router.include_router(groups.router)
router.include_router(jobs.router)
router.include_router(scripts.router)

router.include_router(specifications.router)
router.include_router(spec_blocks.router)
router.include_router(script_templates.router)

router.include_router(pipetask_error_types.router)
router.include_router(pipetask_errors.router)
router.include_router(script_errors.router)

router.include_router(task_sets.router)
router.include_router(product_sets.router)
router.include_router(wms_task_reports.router)

router.include_router(script_dependencies.router)
router.include_router(step_dependencies.router)
router.include_router(queues.router)
5 changes: 3 additions & 2 deletions tests/cli/test_campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@


@pytest.mark.asyncio()
async def test_campaign_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_campaign_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `campaign` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

# generate a uuid to avoid collisions
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -6,9 +7,10 @@
from lsst.cmservice.config import config


def test_commands_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
def test_commands_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test miscellaneous CLI commands"""
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

result = runner.invoke(client_top, "production list")
Expand Down
10 changes: 6 additions & 4 deletions tests/cli/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@


@pytest.mark.asyncio()
async def test_error_create_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_error_create_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test error matching in pipetask_error_type.match.
Correctly match a real error to the error_type database and fail to match a
fake error which is not in the database.
"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

result = runner.invoke(
Expand All @@ -38,10 +39,11 @@ async def test_error_create_cli(uvicorn: UvicornProcess) -> None:


@pytest.mark.asyncio()
async def test_load_error_types_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_load_error_types_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `error_type` db table."""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

result = runner.invoke(client_top, "load error-types --yaml_file examples/error_types.yaml")
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_groups.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid

import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -20,10 +21,11 @@
)


async def test_group_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_group_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `group` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

# generate a uuid to avoid collisions
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_jobs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid

import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -20,10 +21,11 @@
)


async def test_job_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_job_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `job` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

# generate a uuid to avoid collisions
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_others.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -9,10 +10,11 @@
from .util_functions import check_and_parse_result


async def test_others_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_others_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `other` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

result = runner.invoke(client_top, "pipetask_error list --output yaml")
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_productions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid

import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -12,10 +13,11 @@
from .util_functions import cleanup, create_tree


async def test_production_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_production_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `production` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

# generate a uuid to avoid collisions
Expand Down
6 changes: 4 additions & 2 deletions tests/cli/test_steps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid

import pytest
from click.testing import CliRunner
from safir.testing.uvicorn import UvicornProcess

Expand All @@ -20,10 +21,11 @@
)


async def test_step_cli(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_step_cli(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test `step` CLI command"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

# generate a uuid to avoid collisions
Expand Down
5 changes: 3 additions & 2 deletions tests/cli/test_trivial.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@


@pytest.mark.asyncio()
async def test_cli_trivial_campaign(uvicorn: UvicornProcess) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_cli_trivial_campaign(uvicorn: UvicornProcess, api_version: str) -> None:
"""Test fake end to end run using example/example_trivial.yaml"""

client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}"
client_config.service_url = f"{uvicorn.url}{config.asgi.prefix}/{api_version}"
runner = CliRunner()

yaml_file = "examples/example_trivial.yaml"
Expand Down
19 changes: 10 additions & 9 deletions tests/routers/test_campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@


@pytest.mark.asyncio()
async def test_campaign_routes(client: AsyncClient) -> None:
@pytest.mark.parametrize("api_version", ["v1"])
async def test_campaign_routes(client: AsyncClient, api_version: str) -> None:
"""Test `/campaign` API endpoint."""

# generate a uuid to avoid collisions
Expand All @@ -29,9 +30,9 @@ async def test_campaign_routes(client: AsyncClient) -> None:
os.environ["CM_CONFIGS"] = "examples"

# intialize a tree down to one level lower
await create_tree(client, LevelEnum.step, uuid_int)
await create_tree(client, api_version, LevelEnum.step, uuid_int)

response = await client.get(f"{config.asgi.prefix}/campaign/list")
response = await client.get(f"{config.asgi.prefix}/{api_version}/campaign/list")
campaigns = check_and_parse_response(response, list[models.Campaign])
entry = campaigns[0]

Expand All @@ -41,23 +42,23 @@ async def test_campaign_routes(client: AsyncClient) -> None:
)

response = await client.post(
f"{config.asgi.prefix}/load/steps",
f"{config.asgi.prefix}/{api_version}/load/steps",
content=add_steps_query.model_dump_json(),
)
campaign_check = check_and_parse_response(response, models.Campaign)
assert entry.id == campaign_check.id

# check get methods
await check_get_methods(client, entry, "campaign", models.Campaign)
await check_get_methods(client, api_version, entry, "campaign", models.Campaign)

# check update methods
await check_update_methods(client, entry, "campaign", models.Campaign)
await check_update_methods(client, api_version, entry, "campaign", models.Campaign)

# check scripts
await check_scripts(client, entry, "campaign")
await check_scripts(client, api_version, entry, "campaign")

# check queues
await check_queue(client, entry)
await check_queue(client, api_version, entry)

# delete everything we just made in the session
await cleanup(client, check_cascade=True)
await cleanup(client, api_version, check_cascade=True)
Loading

0 comments on commit f1cc697

Please sign in to comment.