Skip to content

Commit 4d5b597

Browse files
authored
Add API examples endpoints (#1006)
2 parents 4b06105 + 695779b commit 4d5b597

File tree

9 files changed

+729
-30
lines changed

9 files changed

+729
-30
lines changed

src/ssvc/api/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# SSVC API Readme
2+
3+
This directory contains source code for the SSVC API.
4+
5+
## Prerequisites
6+
7+
- `uv` CLI tool installed. You can install it via pip:
8+
9+
```shell
10+
pip install uv
11+
```
12+
13+
We recommend using `uv` to manage your Python environment and dependencies,
14+
so you don't need to manually create and activate virtual environments or
15+
worry about Python versions.
16+
17+
## Running a local instance in development mode
18+
19+
From the project root, run:
20+
21+
```shell
22+
uv --project=src run uvicorn ssvc.api.main:app --reload --port=7777
23+
```
24+
25+
> [!TIP]
26+
> Adjust the port as needed.
27+
28+
> [!NOTE]
29+
> We're planning to move our `pyproject.toml` to the top level of the project,
30+
> so in the future you may be able to run this command without the `--project` flag.
31+
32+
This will start the FastAPI server with auto-reload enabled, allowing you to
33+
see changes immediately.
34+
35+
## Running a local instance in production mode
36+
37+
From the project root, run:
38+
39+
```shell
40+
cd docker
41+
docker-compose up api
42+
```
43+
44+
This will start the FastAPI server in a Docker container.
45+
46+
> [!NOTE]
47+
> Docker and Docker Compose must be installed on your machine to use this method.
48+
> Make sure to adjust the `docker-compose.yml` file if you want to change
49+
> the port or other settings.
50+
51+
> [!TIP]
52+
> The `api` docker target copies the code into the container at build time.
53+
> If you make changes to the code, you'll need to rebuild the Docker image
54+
> using `docker-compose build api` before restarting the container. Or else
55+
> use `docker-compose up --build api` to build and start in one command.
56+

src/ssvc/api/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
API for SSVC
44
"""
55

6-
76
# Copyright (c) 2025 Carnegie Mellon University.
87
# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
98
# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS.
@@ -45,12 +44,12 @@
4544
},
4645
)
4746

48-
app.include_router(router_v1)
47+
app.include_router(router_v1, prefix="/ssvc/api/v1", tags=["SSVC API v1"])
4948

5049

5150
# root should redirect to docs
5251
# at least until we have something better to show
53-
@app.get("/", include_in_schema=False)
52+
@app.get("/", include_in_schema=False, description="Redirect to API docs")
5453
async def redirect_root_to_docs():
5554
return RedirectResponse(url="/docs")
5655

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
#!/usr/bin/env python
2+
"""
3+
SSVC API v1 Examples Router
4+
"""
5+
6+
# Copyright (c) 2025 Carnegie Mellon University.
7+
# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
8+
# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS.
9+
# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND,
10+
# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT
11+
# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR
12+
# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE
13+
# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE
14+
# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM
15+
# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
16+
# Licensed under a MIT (SEI)-style license, please see LICENSE or contact
17+
# [email protected] for full terms.
18+
# [DISTRIBUTION STATEMENT A] This material has been approved for
19+
# public release and unlimited distribution. Please see Copyright notice
20+
# for non-US Government use and distribution.
21+
# This Software includes and/or makes use of Third-Party Software each
22+
# subject to its own license.
23+
# DM24-0278
24+
25+
from fastapi import APIRouter
26+
27+
from ssvc.decision_points.base import DecisionPoint, DecisionPointValue
28+
from ssvc.decision_tables.base import DecisionTable
29+
from ssvc.examples import (
30+
EXAMPLE_DECISION_POINT_1,
31+
EXAMPLE_DECISION_TABLE,
32+
EXAMPLE_MINIMAL_DECISION_POINT_VALUE,
33+
EXAMPLE_SELECTION_1,
34+
EXAMPLE_SELECTION_LIST,
35+
)
36+
from ssvc.selection import (
37+
MinimalDecisionPointValue,
38+
Reference,
39+
Selection,
40+
SelectionList,
41+
)
42+
43+
router = APIRouter(prefix="/examples", tags=["Examples"])
44+
45+
# GET to retrieve a sample object
46+
# POST to validate an object against the pydantic model
47+
48+
49+
# Decision Point Values
50+
@router.get(
51+
"/decision-point-values",
52+
response_model=DecisionPointValue,
53+
response_model_exclude_none=True,
54+
summary="Get a sample Decision Point Value",
55+
description="Retrieve a sample Decision Point Value object.",
56+
)
57+
def get_example_decision_point_value() -> DecisionPointValue:
58+
"""
59+
Retrieve a sample Decision Point Value object.
60+
"""
61+
return EXAMPLE_DECISION_POINT_1.values[0]
62+
63+
64+
@router.post(
65+
"/decision-point-values",
66+
response_model=DecisionPointValue,
67+
response_model_exclude_none=True,
68+
summary="Validate a Decision Point Value",
69+
description="Validate a Decision Point Value object against the pydantic model.",
70+
)
71+
def validate_decision_point_value(
72+
decision_point_value: DecisionPointValue,
73+
) -> DecisionPointValue:
74+
"""
75+
Validate a Decision Point Value object against the pydantic model.
76+
"""
77+
return decision_point_value
78+
79+
80+
# Decision Points
81+
@router.get(
82+
"/decision-points",
83+
response_model=DecisionPoint,
84+
response_model_exclude_none=True,
85+
summary="Get a sample Decision Point",
86+
description="Retrieve a sample Decision Point object.",
87+
)
88+
def get_example_decision_point() -> DecisionPoint:
89+
"""
90+
Retrieve a sample Decision Point object.
91+
"""
92+
return EXAMPLE_DECISION_POINT_1
93+
94+
95+
@router.post(
96+
"/decision-points",
97+
response_model=DecisionPoint,
98+
response_model_exclude_none=True,
99+
summary="Validate a Decision Point",
100+
description="Validate a Decision Point object against the pydantic model.",
101+
)
102+
def validate_decision_point(decision_point: DecisionPoint) -> DecisionPoint:
103+
"""
104+
Validate a Decision Point object against the pydantic model.
105+
"""
106+
return decision_point
107+
108+
109+
# Decision Tables
110+
@router.get(
111+
"/decision-tables",
112+
response_model=DecisionTable,
113+
response_model_exclude_none=True,
114+
summary="Get a sample Decision Table",
115+
description="Retrieve a sample Decision Table object.",
116+
)
117+
def get_example_decision_table() -> DecisionTable:
118+
"""
119+
Retrieve a sample Decision Table object.
120+
"""
121+
return EXAMPLE_DECISION_TABLE
122+
123+
124+
@router.post(
125+
"/decision-tables",
126+
response_model=DecisionTable,
127+
response_model_exclude_none=True,
128+
summary="Validate a Decision Table",
129+
description="Validate a Decision Table object against the pydantic model.",
130+
)
131+
def validate_decision_table(decision_table: DecisionTable) -> DecisionTable:
132+
"""
133+
Validate a Decision Table object against the pydantic model.
134+
"""
135+
return decision_table
136+
137+
138+
# minimal decision point values
139+
@router.get(
140+
"/decision-point-values-minimal",
141+
response_model=MinimalDecisionPointValue,
142+
response_model_exclude_none=True,
143+
summary="Get a minimal Decision Point Value",
144+
description="Retrieve a minimal Decision Point Value object.",
145+
)
146+
def get_minimal_decision_point_value() -> MinimalDecisionPointValue:
147+
"""
148+
Retrieve a minimal Decision Point Value object.
149+
"""
150+
return EXAMPLE_MINIMAL_DECISION_POINT_VALUE
151+
152+
153+
@router.post(
154+
"/decision-point-values-minimal",
155+
response_model=MinimalDecisionPointValue,
156+
response_model_exclude_none=True,
157+
summary="Validate a minimal Decision Point Value",
158+
description="Validate a minimal Decision Point Value object against the pydantic model.",
159+
)
160+
def validate_minimal_decision_point_value(
161+
minimal_decision_point_value: MinimalDecisionPointValue,
162+
) -> MinimalDecisionPointValue:
163+
"""
164+
Validate a minimal Decision Point Value object against the pydantic model.
165+
"""
166+
return minimal_decision_point_value
167+
168+
169+
# selection
170+
@router.get(
171+
"/selections",
172+
response_model=Selection,
173+
response_model_exclude_none=True,
174+
summary="Get a sample Selection",
175+
description="Retrieve a sample Selection object.",
176+
)
177+
def get_example_selection() -> Selection:
178+
"""
179+
Retrieve a sample Selection object.
180+
"""
181+
return EXAMPLE_SELECTION_1
182+
183+
184+
@router.post(
185+
"/selections",
186+
response_model=Selection,
187+
response_model_exclude_none=True,
188+
summary="Validate a Selection",
189+
description="Validate a Selection object against the pydantic model.",
190+
)
191+
def validate_selection(selection: Selection) -> Selection:
192+
"""
193+
Validate a Selection object against the pydantic model.
194+
"""
195+
return selection
196+
197+
198+
# Selection lists
199+
@router.get(
200+
"/selection-lists",
201+
response_model=SelectionList,
202+
response_model_exclude_none=True,
203+
summary="Get a sample Selection List",
204+
description="Retrieve a sample Selection List object.",
205+
)
206+
def get_example_selection_list() -> SelectionList:
207+
"""
208+
Retrieve a sample Selection List object.
209+
"""
210+
return EXAMPLE_SELECTION_LIST
211+
212+
213+
@router.post(
214+
"/selection-lists",
215+
response_model=SelectionList,
216+
response_model_exclude_none=True,
217+
summary="Validate a Selection List",
218+
description="Validate a Selection List object against the pydantic model.",
219+
)
220+
def validate_selection_list(selection_list: SelectionList) -> SelectionList:
221+
"""
222+
Validate a Selection List object against the pydantic model.
223+
"""
224+
return selection_list
225+
226+
227+
# references
228+
@router.get(
229+
"/references",
230+
response_model=Reference,
231+
response_model_exclude_none=True,
232+
summary="Get sample References",
233+
description="Retrieve a list of sample Reference URIs.",
234+
)
235+
def get_example_references() -> Reference:
236+
"""
237+
Retrieve a list of sample Reference URIs.
238+
"""
239+
return EXAMPLE_SELECTION_LIST.references[0]
240+
241+
242+
@router.post(
243+
"/references",
244+
response_model=Reference,
245+
response_model_exclude_none=True,
246+
summary="Validate a Reference",
247+
description="Validate a Reference object against the pydantic model.",
248+
)
249+
def validate_reference(reference: Reference) -> Reference:
250+
"""
251+
Validate a Reference object against the pydantic model.
252+
"""
253+
return reference

src/ssvc/api/v1/routers/v1_router.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
decision_point,
2727
decision_table,
2828
decision_tables,
29+
examples,
2930
objects,
3031
)
3132
from ssvc.api.v1.routers import (
@@ -36,7 +37,8 @@
3637
versions,
3738
)
3839

39-
router_v1 = APIRouter(prefix="/v1", tags=["v1"])
40+
router_v1 = APIRouter()
41+
router_v1.include_router(examples.router)
4042
router_v1.include_router(decision_point.router)
4143
router_v1.include_router(decision_points.router)
4244
router_v1.include_router(decision_table.router)

0 commit comments

Comments
 (0)