In my previous post, Creating an API with python: Part 2: MariaDB Database, I set up a MariaDB database and connected it to the five GET endpoints in the FastAPI API. In this post, I’ll add three POST endpoints which will add data to the database.
Prerequisites
- Creating an API with python: Part 1: GET Endpoints
- Creating an API with python: Part 2: MariaDB Database
Step 1: Add schemas.py
Create a new python file called schemas.py. This will contain the pydantic schemas that will be used to validate the POST data for the POST endpoints. They will also provide descriptions for the swagger docs page.
-
Change to the code directory (
~/vboxshare/fastapishould be replaced with the path to your FastAPI python code):$ cd ~/vboxshare/fastapi
-
Change to the manager directory.
$ cd manager
- Create a file called
schemas.pyand add the following to it:from typing import Optional from pydantic import BaseModel, Field class PostLink(BaseModel): link: str = Field(..., description="The link URL") tag: Optional[str] = Field(None, description="Tag name to associate with the link (will be created if it doesn't exist)") tag_id: Optional[str] = Field(None, description="Tag ID to associate with the link (must already exist)") class PostTag(BaseModel): tag: str = Field(..., description="Tag name") class PostTagLink(BaseModel): tag_id: str = Field(..., description="Tag ID (must already exist)") link_id: str = Field(..., description="Link ID (must already exist)")
Step 2: Update manager.py
Update the manager/manager.py file with new functions for creating a link, tag and taglink (create_link, create_tag, create_taglink).
- Open the
manager/manager.pyfile and replace the contents with the following code:from typing import Optional from uuid import uuid4 from sqlalchemy.orm import Session from fastapi import HTTPException from manager import models, schemas def get_link(db: Session, link_id: str): return db.query(models.Link).filter(models.Link.link_id == link_id).first() def get_tag(db: Session, tag_id: str): return db.query(models.Tag).filter(models.Tag.tag_id == tag_id).first() def get_tag_by_tag_name(db: Session, tag: str): return db.query(models.Tag).filter(models.Tag.tag == tag).first() def get_links(db: Session, tag_id: Optional[str] = None, tag: Optional[str] = None): if tag is None and tag_id is None: # TODO: Implement offset and limit return db.query(models.Link).all() elif tag is not None: tag_record = get_tag_by_tag_name(db, tag) if tag_record is None: return [] tag_id = tag_record.tag_id return db.query(models.Link).join(models.TagLink).filter(models.TagLink.tag_id == tag_id).all() def get_tags(db: Session, tag: Optional[str] = None): if tag is None: # TODO: Implement offset and limit return db.query(models.Tag).all() else: db_tag = get_tag_by_tag_name(db, tag) if db_tag is not None: return [db_tag] return [] def get_taglinks(db: Session, tag_id: Optional[str] = None, link_id: Optional[str] = None): if tag_id is None and link_id is None: # TODO: Implement offset and limit return db.query(models.TagLink).all() elif tag_id is not None and link_id is None: return db.query(models.TagLink).filter(models.TagLink.tag_id == tag_id).all() elif tag_id is None and link_id is not None: return db.query(models.TagLink).filter(models.TagLink.link_id == link_id).all() else: return db.query(models.TagLink).filter(models.TagLink.link_id == link_id, models.TagLink.tag_id == tag_id).all() def create_link(db: Session, link: schemas.PostLink): db_link = models.Link(link_id=str(uuid4()), link=link.link) db.add(db_link) tag_id = link.tag_id link_id = db_link.link_id if link.tag is not None: db_tag = get_tag_by_tag_name(db, link.tag) if db_tag is None: db_tag = models.Tag(tag_id=str(uuid4()), tag=link.tag) db.add(db_tag) tag_id = db_tag.tag_id else: tag_id = db_tag.tag_id elif tag_id is not None: db_tag = get_tag(db, tag_id) if db_tag is None: raise HTTPException(status_code=404, detail=f"Tag with tag_id {tag_id} not found") db_taglink = models.TagLink(link_id=link_id, tag_id=tag_id) db.add(db_taglink) db.commit() db.refresh(db_link) return db_link def create_tag(db: Session, tag: schemas.PostTag): db_tag = models.Tag(tag_id=str(uuid4()), tag=tag.tag) db_tag_existing = get_tags(db, tag=tag.tag) if len(db_tag_existing) > 0: raise HTTPException(status_code=409, detail=f"Tag with name {tag.tag} exists") db.add(db_tag) db.commit() db.refresh(db_tag) return db_tag def create_taglink(db: Session, taglink: schemas.PostTagLink): tag_id = taglink.tag_id link_id = taglink.link_id db_taglink = models.TagLink(tag_id=tag_id, link_id=link_id) db_tag_id_existing = get_tag(db, tag_id) if db_tag_id_existing is None: raise HTTPException(status_code=422, detail=f"Tag with tag_id {tag_id} not found") db_link_id_existing = get_link(db, link_id) if db_link_id_existing is None: raise HTTPException(status_code=422, detail=f"Link with link_id {link_id} not found") db_taglink_existing = get_taglinks(db, tag_id=tag_id, link_id=link_id) if len(db_taglink_existing) > 0: raise HTTPException(status_code=409, detail=f"TagLink with tag_id {tag_id} and link_id {link_id} exists") db.add(db_taglink) db.commit() db.refresh(db_taglink) return db_taglink
Step 3: Update main.py
Update main.py with a rollback in the event of a database exception, and add functions post_link, post_tag and post_taglink as the three new POST endpoint functions.
- Change directory to the top level directory:
$ cd ../
- Replace the contents of
main.pywith:from typing import Optional from sqlalchemy.orm import Session from fastapi import FastAPI, HTTPException, Depends from manager import manager, schemas from manager.database import SessionLocal # Dependency def get_db(): db = SessionLocal() try: yield db except Exception: db.rollback() finally: db.close() app = FastAPI() # Get link by link_id @app.get("/link/{link_id}") async def get_link(link_id: str, db: Session = Depends(get_db)): db_link = manager.get_link(db, link_id) if db_link is None: raise HTTPException(status_code=404, detail="Link not found") return db_link # Get links by query params @app.get("/link/") async def get_links(tag_id: Optional[str] = None, tag: Optional[str] = None, db: Session = Depends(get_db)): return manager.get_links(db, tag_id, tag) # Get tag by tag_id @app.get("/tag/{tag_id}") async def get_tag(tag_id: str, db: Session = Depends(get_db)): db_tag = manager.get_tag(db, tag_id) if db_tag is None: raise HTTPException(status_code=404, detail="Tag not found") return db_tag # Get tags by query params @app.get("/tag/") async def get_tags(tag: Optional[str] = None, db: Session = Depends(get_db)): return manager.get_tags(db, tag) # Get taglinks by query params @app.get("/taglink/") async def get_taglinks(link_id: Optional[str] = None, tag_id: Optional[str] = None, db: Session = Depends(get_db)): return manager.get_taglinks(db, tag_id, link_id) # Post a link @app.post("/link/") async def post_link(link: schemas.PostLink, db: Session = Depends(get_db)): if link.tag is None and link.tag_id is None: raise HTTPException(status_code=422, detail="One of tag_id or tag must be specified") if link.tag is not None and link.tag_id is not None: raise HTTPException(status_code=422, detail="Only one of tag_id or tag must be specified") db_link = manager.create_link(db, link) return db_link # Post a tag @app.post("/tag/") async def post_tag(tag: schemas.PostTag, db: Session = Depends(get_db)): db_tag = manager.create_tag(db, tag) return db_tag # Post a taglink @app.post("/taglink/") async def post_taglink(taglink: schemas.PostTagLink, db: Session = Depends(get_db)): db_tag = manager.create_taglink(db, taglink) return db_tag
Step 4: Update test script
Now we need to update the test script to test the POST endpoints, retrieve the responses and test the GET endpoints with the data retrieved.
- Open the file
test.sh, and replace the code with the following:IP=$1 BASEURL=http://$IP:8000 echo "==========================" echo "Test POST /link link=https://www.test1.com, tag=test1" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link\": \"https://www.test1.com\", \"tag\": \"test1\"}" $BASEURL/link/) echo $resp link_id1=$(echo $resp | jq -r '.link_id') echo $link_id1 echo "==========================" echo "Test GET /tag tag=test1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/tag/?tag=test1) echo $resp tag_id1=$(echo $resp | jq -r '.[0].tag_id') echo $tag_id1 echo "==========================" echo "Test POST /tag tag=test2" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"tag\": \"test2\"}" $BASEURL/tag/) echo $resp tag_id2=$(echo $resp | jq -r '.tag_id') echo $tag_id2 echo "==========================" echo "Test POST /link link=https://www.test2.com, tag=test2" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link\": \"https://www.test2.com\", \"tag\": \"test2\"}" $BASEURL/link/) echo $resp link_id2=$(echo $resp | jq -r '.link_id') echo $link_id2 echo "==========================" echo "Test POST /link link=https://www.test2.com, tag_id=$tag_id1" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link\": \"https://www.test2.com\", \"tag_id\": \"$tag_id1\"}" $BASEURL/link/) echo $resp link_id2=$(echo $resp | jq -r '.link_id') echo $link_id2 echo "==========================" echo "Test POST /link link=https://www.test2.com, tag_id=invalid. Expect 404 response: Tag with tag_id invalid not found" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link\": \"https://www.test2.com\", \"tag_id\": \"invalid\"}" $BASEURL/link/) echo $resp echo "==========================" echo "Test GET /link" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/link/) echo $resp echo "==========================" echo "Test GET /link tag_id=$tag_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/link/?tag_id=$tag_id1) echo $resp echo "==========================" echo "Test GET /link tag=test1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/link/?tag=test1) echo $resp echo "==========================" echo "Test GET /link link_id=$link_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/link/$link_id1) echo $resp echo "==========================" echo "Test GET /tag tag_id=$tag_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/tag/$tag_id1) echo $resp echo "==========================" echo "Test GET /tag" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/tag/) echo $resp echo "==========================" echo "Test POST /taglink tag_id=$tag_id2, link_id=$link_id1" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link_id\": \"$link_id1\", \"tag_id\": \"$tag_id2\"}" $BASEURL/taglink/) echo $resp echo "==========================" echo "Test POST /taglink tag_id=invalid, link_id=$link_id1. Expect 422 response: Tag with tag_id invalid not found" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link_id\": \"$link_id1\", \"tag_id\": \"invalid\"}" $BASEURL/taglink/) echo $resp echo "==========================" echo "Test POST /taglink tag_id=$tag_id1, link_id=invalid. Expect 422 response: Link with link_id invalid not found" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link_id\": \"invalid\", \"tag_id\": \"$tag_id1\"}" $BASEURL/taglink/) echo $resp echo "==========================" echo "Test POST /taglink tag_id=$tag_id1, link_id=$link_id1. Expect 409 response: TagLink with tag_id $tag_id1 and link_id $link_id1 exists" resp=$(curl -X "POST" -H "Content-Type: application/json" -d "{\"link_id\": \"$link_id1\", \"tag_id\": \"$tag_id1\"}" $BASEURL/taglink/) echo $resp echo "==========================" echo "Test GET /taglink" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/taglink/) echo $resp echo "==========================" echo "Test GET /taglink link_id=$link_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/taglink/?link_id=$link_id1) echo $resp echo "==========================" echo "Test GET /taglink tag_id=$tag_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/taglink/?tag_id=$tag_id1) echo $resp echo "==========================" echo "Test GET /taglink tag_id=$tag_id1 link_id=$link_id1" resp=$(curl -X "GET" -H "Content-Type: application/json" $BASEURL/taglink/?tag_id=$tag_id1&link_id=$link_id1) echo $resp - Make sure the test script is executable:
$ chmod ugo+x test.sh
Step 5: Run the test script
- Run the test script, replacing
YOUR_IPwith the IP of your API host server (or127.0.0.1if running the test script from the machine that is hosting the API):$ ./test.sh YOUR_IP
If all goes well, you should see an output of json responses to each request, with no errors, other than those expected in the test descriptions. - If you want to run the test script again, you will need to clear the test data from the database first. To do this, open a mysql terminal:
$ mysql -u root -p
Enter the password you set with the initial set-up of MariaDB in Part 2. -
Switch to the apiservice database:
MariaDB [(none)]> USE apiservice;
-
Run the following to clear the test data:
MariaDB [(apiservice)]> DELETE from taglink where link_id IN(SELECT link_id from link where link LIKE '%test%'); DELETE from taglink where tag_id IN(SELECT tag_id from tag where tag LIKE '%test%'); DELETE from tag where tag LIKE '%test%'; DELETE from link where link LIKE '%test%';
You should now be able to run the test script again.
Conclusion
You should now have a FastAPI API running with five GET endpoints and three POST endpoints, that call into a MariaDB database to retrieve and insert data.
To add DELETE endpoints, see my follow-on post, Creating an API with python: part 4: DELETE Endpoints.
Thanks for reading!

1 Response
[…] Creating an API with python: Part 3: POST Endpoints […]