-
Notifications
You must be signed in to change notification settings - Fork 42
Add API examples endpoints #1006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
fc7b9d2
add examples to api
ahouseholder 87224c2
add EXAMPLE_MINIMAL_DECISION_POINT_VALUE to examples
ahouseholder 7ecedd3
add test for examples in app
ahouseholder c20e4a2
add endpoint to retrieve sample references and corresponding tests
ahouseholder 8e59824
mark test_get_selection_lists as expected failure and add TODO for PO…
ahouseholder 17b6845
add POST endpoint to validate Reference and corresponding tests
ahouseholder 3ea12b4
Merge branch 'main' of https://github.com/CERTCC/SSVC into api-examples
ahouseholder bb41416
remove debug print statement from data cleanup function
ahouseholder 650e391
add tests for SelectionList to validate empty lists and model dumping…
ahouseholder 054261f
refactor examples and tests for clarity and structure
ahouseholder e2a67bc
add model validators to handle default summary and remove falsy fields
ahouseholder 1c36e52
Merge branch 'main' into api-examples
ahouseholder 20405dc
add prefix and tags to API router and enhance root redirect description
ahouseholder 87041a6
remove prefix and tags from v1 API router configuration
ahouseholder 2a88e43
add README.md for SSVC API setup and usage instructions
ahouseholder 87fb9e0
Merge remote-tracking branch 'gh_pub/api-examples' into api-examples
ahouseholder f44a2f3
Update src/test/api/routers/test_examples.py
ahouseholder 83161ad
black formatter
ahouseholder 66ed04a
Merge remote-tracking branch 'gh_pub/api-examples' into api-examples
ahouseholder 96b7b53
Update src/ssvc/selection.py
ahouseholder d29d624
Update src/test/test_selections.py
ahouseholder 695779b
Update src/test/test_selections.py
ahouseholder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # SSVC API Readme | ||
|
|
||
| This directory contains source code for the SSVC API. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - `uv` CLI tool installed. You can install it via pip: | ||
|
|
||
| ```shell | ||
| pip install uv | ||
| ``` | ||
|
|
||
| We recommend using `uv` to manage your Python environment and dependencies, | ||
| so you don't need to manually create and activate virtual environments or | ||
| worry about Python versions. | ||
|
|
||
| ## Running a local instance in development mode | ||
|
|
||
| From the project root, run: | ||
|
|
||
| ```shell | ||
| uv --project=src run uvicorn ssvc.api.main:app --reload --port=7777 | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > Adjust the port as needed. | ||
|
|
||
| > [!NOTE] | ||
| > We're planning to move our `pyproject.toml` to the top level of the project, | ||
| > so in the future you may be able to run this command without the `--project` flag. | ||
|
|
||
| This will start the FastAPI server with auto-reload enabled, allowing you to | ||
| see changes immediately. | ||
|
|
||
| ## Running a local instance in production mode | ||
|
|
||
| From the project root, run: | ||
|
|
||
| ```shell | ||
| cd docker | ||
| docker-compose up api | ||
| ``` | ||
|
|
||
| This will start the FastAPI server in a Docker container. | ||
|
|
||
| > [!NOTE] | ||
| > Docker and Docker Compose must be installed on your machine to use this method. | ||
| > Make sure to adjust the `docker-compose.yml` file if you want to change | ||
| > the port or other settings. | ||
|
|
||
| > [!TIP] | ||
| > The `api` docker target copies the code into the container at build time. | ||
| > If you make changes to the code, you'll need to rebuild the Docker image | ||
| > using `docker-compose build api` before restarting the container. Or else | ||
| > use `docker-compose up --build api` to build and start in one command. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| #!/usr/bin/env python | ||
| """ | ||
| SSVC API v1 Examples Router | ||
| """ | ||
|
|
||
| # Copyright (c) 2025 Carnegie Mellon University. | ||
| # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE | ||
| # ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. | ||
| # CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, | ||
| # EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT | ||
| # NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR | ||
| # MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE | ||
| # OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE | ||
| # ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM | ||
| # PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. | ||
| # Licensed under a MIT (SEI)-style license, please see LICENSE or contact | ||
| # [email protected] for full terms. | ||
| # [DISTRIBUTION STATEMENT A] This material has been approved for | ||
| # public release and unlimited distribution. Please see Copyright notice | ||
| # for non-US Government use and distribution. | ||
| # This Software includes and/or makes use of Third-Party Software each | ||
| # subject to its own license. | ||
| # DM24-0278 | ||
|
|
||
| from fastapi import APIRouter | ||
|
|
||
| from ssvc.decision_points.base import DecisionPoint, DecisionPointValue | ||
| from ssvc.decision_tables.base import DecisionTable | ||
| from ssvc.examples import ( | ||
| EXAMPLE_DECISION_POINT_1, | ||
| EXAMPLE_DECISION_TABLE, | ||
| EXAMPLE_MINIMAL_DECISION_POINT_VALUE, | ||
| EXAMPLE_SELECTION_1, | ||
| EXAMPLE_SELECTION_LIST, | ||
| ) | ||
| from ssvc.selection import ( | ||
| MinimalDecisionPointValue, | ||
| Reference, | ||
| Selection, | ||
| SelectionList, | ||
| ) | ||
|
|
||
| router = APIRouter(prefix="/examples", tags=["Examples"]) | ||
|
|
||
| # GET to retrieve a sample object | ||
| # POST to validate an object against the pydantic model | ||
|
|
||
|
|
||
| # Decision Point Values | ||
| @router.get( | ||
| "/decision-point-values", | ||
| response_model=DecisionPointValue, | ||
| response_model_exclude_none=True, | ||
| summary="Get a sample Decision Point Value", | ||
| description="Retrieve a sample Decision Point Value object.", | ||
| ) | ||
| def get_example_decision_point_value() -> DecisionPointValue: | ||
| """ | ||
| Retrieve a sample Decision Point Value object. | ||
| """ | ||
| return EXAMPLE_DECISION_POINT_1.values[0] | ||
|
|
||
|
|
||
| @router.post( | ||
| "/decision-point-values", | ||
| response_model=DecisionPointValue, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Decision Point Value", | ||
| description="Validate a Decision Point Value object against the pydantic model.", | ||
| ) | ||
| def validate_decision_point_value( | ||
| decision_point_value: DecisionPointValue, | ||
| ) -> DecisionPointValue: | ||
| """ | ||
| Validate a Decision Point Value object against the pydantic model. | ||
| """ | ||
| return decision_point_value | ||
|
|
||
|
|
||
| # Decision Points | ||
| @router.get( | ||
| "/decision-points", | ||
| response_model=DecisionPoint, | ||
| response_model_exclude_none=True, | ||
| summary="Get a sample Decision Point", | ||
| description="Retrieve a sample Decision Point object.", | ||
| ) | ||
| def get_example_decision_point() -> DecisionPoint: | ||
| """ | ||
| Retrieve a sample Decision Point object. | ||
| """ | ||
| return EXAMPLE_DECISION_POINT_1 | ||
|
|
||
|
|
||
| @router.post( | ||
| "/decision-points", | ||
| response_model=DecisionPoint, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Decision Point", | ||
| description="Validate a Decision Point object against the pydantic model.", | ||
| ) | ||
| def validate_decision_point(decision_point: DecisionPoint) -> DecisionPoint: | ||
| """ | ||
| Validate a Decision Point object against the pydantic model. | ||
| """ | ||
| return decision_point | ||
|
|
||
|
|
||
| # Decision Tables | ||
| @router.get( | ||
| "/decision-tables", | ||
| response_model=DecisionTable, | ||
| response_model_exclude_none=True, | ||
| summary="Get a sample Decision Table", | ||
| description="Retrieve a sample Decision Table object.", | ||
| ) | ||
| def get_example_decision_table() -> DecisionTable: | ||
| """ | ||
| Retrieve a sample Decision Table object. | ||
| """ | ||
| return EXAMPLE_DECISION_TABLE | ||
|
|
||
|
|
||
| @router.post( | ||
| "/decision-tables", | ||
| response_model=DecisionTable, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Decision Table", | ||
| description="Validate a Decision Table object against the pydantic model.", | ||
| ) | ||
| def validate_decision_table(decision_table: DecisionTable) -> DecisionTable: | ||
| """ | ||
| Validate a Decision Table object against the pydantic model. | ||
| """ | ||
| return decision_table | ||
|
|
||
|
|
||
| # minimal decision point values | ||
| @router.get( | ||
| "/decision-point-values-minimal", | ||
| response_model=MinimalDecisionPointValue, | ||
| response_model_exclude_none=True, | ||
| summary="Get a minimal Decision Point Value", | ||
| description="Retrieve a minimal Decision Point Value object.", | ||
| ) | ||
| def get_minimal_decision_point_value() -> MinimalDecisionPointValue: | ||
| """ | ||
| Retrieve a minimal Decision Point Value object. | ||
| """ | ||
| return EXAMPLE_MINIMAL_DECISION_POINT_VALUE | ||
|
|
||
|
|
||
| @router.post( | ||
| "/decision-point-values-minimal", | ||
| response_model=MinimalDecisionPointValue, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a minimal Decision Point Value", | ||
| description="Validate a minimal Decision Point Value object against the pydantic model.", | ||
| ) | ||
| def validate_minimal_decision_point_value( | ||
| minimal_decision_point_value: MinimalDecisionPointValue, | ||
| ) -> MinimalDecisionPointValue: | ||
| """ | ||
| Validate a minimal Decision Point Value object against the pydantic model. | ||
| """ | ||
| return minimal_decision_point_value | ||
|
|
||
|
|
||
| # selection | ||
| @router.get( | ||
| "/selections", | ||
| response_model=Selection, | ||
| response_model_exclude_none=True, | ||
| summary="Get a sample Selection", | ||
| description="Retrieve a sample Selection object.", | ||
| ) | ||
| def get_example_selection() -> Selection: | ||
| """ | ||
| Retrieve a sample Selection object. | ||
| """ | ||
| return EXAMPLE_SELECTION_1 | ||
|
|
||
|
|
||
| @router.post( | ||
| "/selections", | ||
| response_model=Selection, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Selection", | ||
| description="Validate a Selection object against the pydantic model.", | ||
| ) | ||
| def validate_selection(selection: Selection) -> Selection: | ||
| """ | ||
| Validate a Selection object against the pydantic model. | ||
| """ | ||
| return selection | ||
|
|
||
|
|
||
| # Selection lists | ||
| @router.get( | ||
| "/selection-lists", | ||
| response_model=SelectionList, | ||
| response_model_exclude_none=True, | ||
| summary="Get a sample Selection List", | ||
| description="Retrieve a sample Selection List object.", | ||
| ) | ||
| def get_example_selection_list() -> SelectionList: | ||
| """ | ||
| Retrieve a sample Selection List object. | ||
| """ | ||
| return EXAMPLE_SELECTION_LIST | ||
|
|
||
|
|
||
| @router.post( | ||
| "/selection-lists", | ||
| response_model=SelectionList, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Selection List", | ||
| description="Validate a Selection List object against the pydantic model.", | ||
| ) | ||
| def validate_selection_list(selection_list: SelectionList) -> SelectionList: | ||
| """ | ||
| Validate a Selection List object against the pydantic model. | ||
| """ | ||
| return selection_list | ||
|
|
||
|
|
||
| # references | ||
| @router.get( | ||
| "/references", | ||
| response_model=Reference, | ||
| response_model_exclude_none=True, | ||
| summary="Get sample References", | ||
| description="Retrieve a list of sample Reference URIs.", | ||
| ) | ||
| def get_example_references() -> Reference: | ||
| """ | ||
| Retrieve a list of sample Reference URIs. | ||
| """ | ||
| return EXAMPLE_SELECTION_LIST.references[0] | ||
|
|
||
|
|
||
| @router.post( | ||
| "/references", | ||
| response_model=Reference, | ||
| response_model_exclude_none=True, | ||
| summary="Validate a Reference", | ||
| description="Validate a Reference object against the pydantic model.", | ||
| ) | ||
| def validate_reference(reference: Reference) -> Reference: | ||
| """ | ||
| Validate a Reference object against the pydantic model. | ||
| """ | ||
| return reference |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
This changes the prefix for the whole API. If we don't want to force the
/ssvc/apiprefix (or we'd rather it just be/ssvc/v1), we should change this line.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, instead of having
ssvc.api.v1.routers.v1_router.routerassign the/v1part of the prefix, this PR changes it so the/v1prefix is added by theinclude_routercall. I'm not sure which way is preferable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no preference really. I think it is fine the way you have it.