generated from ministryofjustice/template-repository
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7634a2d
commit b69cb78
Showing
3 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
@@ -1,3 +1,7 @@ | ||
--- | ||
title: Case model | ||
--- | ||
|
||
# Cases | ||
|
||
### Create a case | ||
|
This file contains 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,73 @@ | ||
--- | ||
title: Models | ||
--- | ||
|
||
# What are models? | ||
Models are classes which define both the API and database schema. | ||
|
||
Each field has Python type annotations which defines what data is accepted by the API | ||
as well as the column type in the database. | ||
|
||
Every object created from these classes is validated by Pydantic, which ensures that the types of every object matches | ||
the type defined in the model, always. | ||
|
||
View the SQLModel documentation [here](https://sqlmodel.tiangolo.com/). | ||
|
||
## What data types can I use in models? | ||
All datatypes supported by Pydantic can be used, and you can define your own if needed. | ||
|
||
View the Pydantic documentation [here](https://docs.pydantic.dev/latest/concepts/types/#constrained-types). | ||
|
||
To allow for a set of values you can define an enum and use this in your model. | ||
|
||
```python | ||
from enum import Enum | ||
from sqlmodel import SQLModel, Field | ||
|
||
|
||
class Month(str, Enum): | ||
# jan will be stored in the database and "January" will be an acceptable input. | ||
jan = "January" | ||
fed = "February" | ||
mar = "March" | ||
|
||
|
||
class Birthday(SQLModel): | ||
day: int = Field(gt=0, le=31) # A number greater than 0 but less than or equal to 31. | ||
month: Month # Valid inputs would be "January", "February", "March" | ||
``` | ||
|
||
## How do I create a database table? | ||
By default, models will not be created as a table in the database. | ||
|
||
This is because we use models to define more than the database, they also define request and response | ||
models from the API. | ||
|
||
To create a table with the schema defined by your model set the `table` argument on your class to True. | ||
|
||
```python | ||
from sqlmodel import SQLModel | ||
|
||
|
||
class MyModel(SQLModel, table=True): | ||
name: str | ||
age: int | ||
``` | ||
|
||
To reflect this new model in the database you will need to generate and run an Alembic migration. | ||
|
||
## How do I use these models to define endpoint schemas? | ||
By implementing them as part of your routers' method arguments they are automatically used to validate the user's input. | ||
|
||
```python | ||
from app.models.cases import Case, CaseRequest | ||
from app.routers.case_information import router | ||
|
||
|
||
@router.post("/", tags=["cases"], response_model=Case) | ||
def create_case(request: CaseRequest): | ||
pass | ||
``` | ||
|
||
### Model documentation | ||
Your models will automatically be added to the Swagger documentation when you use them as part of an API endpoint. |
This file contains 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,83 @@ | ||
--- | ||
title: Endpoints | ||
--- | ||
|
||
# Creating new endpoints | ||
To create new endpoints you need to define the URL route it belongs to and which request method it accepts. | ||
|
||
## APIRouter | ||
Each route needs to belong to an APIRouter. APIRouters are analogous to Flask Blueprints in that they group endpoints | ||
together and can be assigned shared a prefix, tags and other attributes. | ||
|
||
See [here](https://fastapi.tiangolo.com/reference/apirouter/) for the documentation on the APIRouter class. | ||
|
||
```python | ||
from fastapi import APIRouter | ||
from sqlmodel import SQLModel | ||
from app.models.types.categories import Categories | ||
|
||
router = APIRouter(prefix="/case", | ||
tags=["cases"]) | ||
|
||
|
||
@router.get("/{case_id}") | ||
async def read_case(case_id: int): | ||
# We will discuss how to read from the database below. | ||
case = {"id": 1, | ||
"name": "test", | ||
"category": "Housing"} | ||
return case | ||
|
||
|
||
class Case(SQLModel): | ||
name: str # Pydantic ensures the name is always a string | ||
category: Categories | None = None # This means that the category must be of the type Categories or None | ||
|
||
|
||
@router.post("/") | ||
async def create_case(case: Case): | ||
return case | ||
``` | ||
These create_case route only accepts post requests and requires the request body to be of the form defined by | ||
the Case model. | ||
|
||
This means the body needs to contain a name which can be encoded as a string and a category which | ||
matches one of the categories of law defined in the models enum class. | ||
|
||
|
||
## Reading and writing to the database | ||
Database connections are managed with SQLModel sessions, these are inherited from SQLAlchemy sessions. | ||
|
||
If your endpoint depends on a database session you can pass this into your routing function using the | ||
FastAPI `Depends()` method. | ||
|
||
```python | ||
from fastapi import APIRouter, HTTPException, Depends | ||
from app.models.cases import CaseRequest, Case | ||
from sqlmodel import Session, select | ||
from app.db import get_session | ||
from datetime import datetime | ||
|
||
router = APIRouter( | ||
prefix="/cases", | ||
tags=["cases"], | ||
responses={404: {"description": "Not found"}}, | ||
) | ||
|
||
|
||
@router.get("/{case_id}", tags=["cases"]) | ||
async def read_case(case_id: str, session: Session = Depends(get_session)): | ||
case = session.get(Case, case_id) | ||
if not case: | ||
raise HTTPException(status_code=404, detail="Case not found") | ||
return case | ||
|
||
|
||
@router.post("/", tags=["cases"], response_model=Case) | ||
def create_case(request: CaseRequest, session: Session = Depends(get_session)): | ||
case = Case(category=request.category, time=datetime.now(), name=request.name, id=1) | ||
session.add(case) | ||
session.commit() | ||
session.refresh(case) | ||
return case | ||
``` |