This article explores querying a post by its ID using SQLAlchemy, transitioning from raw SQL for improved efficiency and maintainability.
In this article, we will explore how to query an individual post by its unique ID using SQLAlchemy. We transition from using raw SQL queries to leveraging SQLAlchemy’s ORM for improved abstraction, efficiency, and maintainability in database operations.
Previously, a GET endpoint for retrieving a single post was implemented using raw SQL as shown below:
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: str): cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),)) post = cursor.fetchone() if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found") return {"post_detail": post}
Similarly, an endpoint to delete a post was defined as:
We will now replace these raw SQL operations with SQLAlchemy ORM queries. First, it is crucial to ensure that the database dependency is correctly injected into the function and that the post ID is passed as an integer.
Below is the modified GET endpoint using SQLAlchemy. Notice that the original raw SQL statements are retained as comments for reference:
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: int, db: Session = Depends(get_db)): # cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),)) # post = cursor.fetchone() # Query the Post model filtering for the matching id. if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found") return {"post_detail": post}
In the creation endpoint, after showing the legacy raw SQL usage as a comment, we first commit the connection and then create a new post using the Post model:
When querying for a specific post, the goal is to mimic filtering by the post ID (i.e., the WHERE clause). Initially, the code was structured like this:
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: int, db: Session = Depends(get_db)): # cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),)) db.query if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found")
To clarify the process, we first assign the query result to a variable and print it for debugging:
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: int, db: Session = Depends(get_db)): # cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),)) post = db.query(models.Post).filter(models.Post.id == id) print(post) if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found") return {"post_detail": post}
After testing, you might encounter errors because the query is incomplete without evaluation. The correct approach is to fetch the first matching record using the .first() method:
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: int, db: Session = Depends(get_db)): # cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),)) # Using .first() returns the first instance that matches the filter condition. post = db.query(models.Post).filter(models.Post.id == id).first() print(post) if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found") return {"post_detail": post}
After confirming that the query works correctly, remember to remove debugging print statements from your production code.
After saving and testing these changes, a successful GET request for a post will return a JSON output similar to the following:
Copy
Ask AI
{ "post_detail": { "id": 4, "title": "hey this is my new post", "content": "something something beaches", "published": true, "created_at": "2021-08-21T23:34:18.169278-04:00" }}
SQLAlchemy will also log the exact SQL query executed, resembling:
Copy
Ask AI
SELECT posts.id AS posts_id, posts.title AS posts_title, posts.content AS posts_content, posts.published AS posts_published, posts.created_at AS posts_created_atFROM postsWHERE posts.id = %(id_1)s
When trying to fetch a non-existent post (for example, with ID 666), the API returns a 404 error response:
Copy
Ask AI
{ "detail": "post with id: 666 was not found"}
Ensure that your error handling covers all edge cases to avoid exposing sensitive details about your database.
With these updates, the endpoint for fetching an individual post is now optimized, fully utilizing SQLAlchemy for database interactions, and is more maintainable for future improvements.