-
Notifications
You must be signed in to change notification settings - Fork 5
Implemented In-Memory CRUD API for Book Management in Flask #2
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
base: main
Are you sure you want to change the base?
Changes from all commits
45cb50d
7a44ecb
a8325d9
e73ba49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||
| # Use an official Python runtime as the base image | ||||||||||||
| FROM python:3.11-slim | ||||||||||||
|
|
||||||||||||
| # Set the working directory in the container | ||||||||||||
| WORKDIR /app | ||||||||||||
|
|
||||||||||||
| # Copy the current directory contents into the container at /app | ||||||||||||
| COPY . /app | ||||||||||||
|
|
||||||||||||
| # Install any needed packages specified in requirements.txt | ||||||||||||
| RUN pip install --no-cache-dir -r requirements.txt | ||||||||||||
|
|
||||||||||||
| # Expose port 5000 for Flask to run on | ||||||||||||
| EXPOSE 5000 | ||||||||||||
|
|
||||||||||||
| # Define the environment variable for Flask | ||||||||||||
| ENV FLASK_APP=app.py | ||||||||||||
| ENV FLASK_RUN_HOST=0.0.0.0 | ||||||||||||
|
|
||||||||||||
| # Run the Flask application | ||||||||||||
| CMD ["flask", "run"] | ||||||||||||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider using ENTRYPOINT with CMD for more flexibility. While the current CMD instruction works, using ENTRYPOINT with CMD can provide more flexibility and clarity. Consider updating the run command as follows: -CMD ["flask", "run"]
+ENTRYPOINT ["flask"]
+CMD ["run", "--host=0.0.0.0"]This change offers several benefits:
This approach maintains the current functionality while improving flexibility for different runtime scenarios. 📝 Committable suggestion
Suggested change
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from flask import Flask | ||
| from flask_cors import CORS | ||
| from routes import book_routes | ||
| from config import DevelopmentConfig | ||
|
|
||
|
|
||
| app = Flask(__name__) | ||
| CORS(app) | ||
|
|
||
| # Register the blueprints | ||
| app.register_blueprint(book_routes) | ||
|
|
||
| app.config.from_object(DevelopmentConfig) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app.run() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class DevelopmentConfig: | ||
| DEBUG = True | ||
| CORS_HEADERS = 'Content-Type' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Incomplete CORS Configuration Detected The current CORS setup only specifies the
Recommendation: Consider adding 🔗 Analysis chainReview CORS configuration for completeness. The current CORS configuration only specifies the To check if there are any CORS-related configurations elsewhere in the project, you can run the following script: Update on CORS Configuration Verification The previous search encountered an issue with recognizing Python files. I've adjusted the search to target Retrying CORS Configuration Search with Correct Previously, the search for CORS-related configurations failed due to incorrect usage of the 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Search for CORS-related configurations in Python files
# Test: Look for CORS-related imports and configurations
rg --type python -i '(from flask_cors import CORS|CORS\(|cors_origins|cors_headers|cors_methods)'
Length of output: 130 Script: #!/bin/bash
# Description: Search for CORS-related configurations in Python files by targeting .py extensions
rg -i '(from flask_cors import CORS|CORS\(|cors_origins|cors_headers|cors_methods)' -- '*.py'
Length of output: 140 Script: #!/bin/bash
# Description: Search for CORS-related configurations in Python files using correct glob pattern
rg -i '(from flask_cors import CORS|CORS\(|cors_origins|cors_headers|cors_methods)' --glob '*.py'
Length of output: 195 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from uuid import uuid4 | ||
|
|
||
|
|
||
| class Book: | ||
| def __init__(self, title, author, price, category, publication_year): | ||
| self.id = str(uuid4()) | ||
| self.title = title | ||
| self.author = author | ||
| self.price = price | ||
| self.category = category | ||
| self.publication_year = publication_year | ||
|
Comment on lines
+4
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider adding type hints, docstring, and using Decimal for price. The
from decimal import Decimal
from uuid import uuid4
class Book:
def __init__(self, title: str, author: str, price: Decimal, category: str, publication_year: int):
self.id: str = str(uuid4())
self.title: str = title
self.author: str = author
self.price: Decimal = price
self.category: str = category
self.publication_year: int = publication_year
class Book:
"""
Represents a book in the inventory.
Attributes:
id (str): Unique identifier for the book.
title (str): The title of the book.
author (str): The author of the book.
price (Decimal): The price of the book.
category (str): The category or genre of the book.
publication_year (int): The year the book was published.
"""
from decimal import Decimal
# In the __init__ method:
self.price: Decimal = Decimal(price)These changes will improve code readability, maintainability, and potentially prevent issues related to financial calculations. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Flask==3.0.3 | ||
| Flask-Cors==5.0.0 | ||
| Werkzeug==3.0.4 | ||
|
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider using version ranges and adding production/development dependencies The current
Would you like me to provide a complete example of separated production and development requirement files? |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||||||||||||
| from flask import Blueprint, jsonify, request | ||||||||||||||||||||
| from uuid import uuid4 | ||||||||||||||||||||
| from validators import validate_book | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| book_routes = Blueprint('book_routes', __name__) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # In-memory data structure | ||||||||||||||||||||
| books = [] | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.route('/books', methods=['GET']) | ||||||||||||||||||||
| def get_all_books(): | ||||||||||||||||||||
| return jsonify(books), 200 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.route('/books/<string:book_id>', methods=['GET']) | ||||||||||||||||||||
| def get_book_by_id(book_id): | ||||||||||||||||||||
| for book in books: | ||||||||||||||||||||
| if book['id'] == book_id: | ||||||||||||||||||||
| return jsonify(book), 200 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return jsonify({"error": "Book not found"}), 404 | ||||||||||||||||||||
|
Comment on lines
+19
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor repetitive book lookup code into a helper function The logic for finding a book by ID is repeated in multiple endpoints ( Create a helper function def find_book_by_id(book_id):
for book in books:
if book['id'] == book_id:
return book
return NoneUpdate your endpoints to use the helper function: In def get_book_by_id(book_id):
- for book in books:
- if book['id'] == book_id:
- return jsonify(book), 200
-
- return jsonify({"error": "Book not found"}), 404
+ book = find_book_by_id(book_id)
+ if book:
+ return jsonify(book), 200
+ else:
+ return jsonify({"error": "Book not found"}), 404In def update_book_by_id(book_id):
data = request.get_json()
# ... [validation code] ...
- for book in books:
- if book['id'] == book_id:
- data.pop('id', None)
- book.update(data)
- return jsonify(book), 200
-
- return jsonify({"error": "Book not found"}), 404
+ book = find_book_by_id(book_id)
+ if book:
+ data.pop('id', None)
+ book.update(data)
+ return jsonify(book), 200
+ else:
+ return jsonify({"error": "Book not found"}), 404In def delete_book_by_id(book_id):
- for book in books:
- if book['id'] == book_id:
- books.remove(book)
- return jsonify({"message": "Book deleted"}), 200
-
- return jsonify({"error": "Book not found"}), 404
+ book = find_book_by_id(book_id)
+ if book:
+ books.remove(book)
+ return jsonify({"message": "Book deleted"}), 200
+ else:
+ return jsonify({"error": "Book not found"}), 404Also applies to: 61-67, 71-77 |
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.route('/books', methods=['POST']) | ||||||||||||||||||||
| def add_book(): | ||||||||||||||||||||
| data = request.get_json() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if not data: | ||||||||||||||||||||
| return jsonify({"error": "No data provided."}), 400 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| validation_error = validate_book(data) | ||||||||||||||||||||
| if validation_error: | ||||||||||||||||||||
| return jsonify(validation_error), validation_error[1] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| new_book = { | ||||||||||||||||||||
| 'id': str(uuid4()), | ||||||||||||||||||||
| 'title': data['title'], | ||||||||||||||||||||
| 'author': data['author'], | ||||||||||||||||||||
| 'price': data['price'], | ||||||||||||||||||||
| 'category': data['category'], | ||||||||||||||||||||
| 'publication_year': data['publication_year'] | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+37
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure user-provided 'id' is not used when adding a new book Currently, if the user provides an Consider removing the def add_book():
data = request.get_json()
if not data:
return jsonify({"error": "No data provided."}), 400
validation_error = validate_book(data)
if validation_error:
return jsonify(validation_error), validation_error[1]
+ data.pop('id', None) # Remove 'id' if present in data
new_book = {
'id': str(uuid4()),
'title': data['title'],
'author': data['author'],
'price': data['price'],
'category': data['category'],
'publication_year': data['publication_year']
}
|
||||||||||||||||||||
| books.append(new_book) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return jsonify(new_book), 201 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.route('/books/<string:book_id>', methods=['PUT']) | ||||||||||||||||||||
| def update_book_by_id(book_id): | ||||||||||||||||||||
| data = request.get_json() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if not data: | ||||||||||||||||||||
| return jsonify({"error": "No data provided."}), 400 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| validation_error = validate_book(data) | ||||||||||||||||||||
| if validation_error: | ||||||||||||||||||||
| return jsonify(validation_error), validation_error[1] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for book in books: | ||||||||||||||||||||
| if book['id'] == book_id: | ||||||||||||||||||||
| book.update(data) | ||||||||||||||||||||
| return jsonify(book), 200 | ||||||||||||||||||||
|
Comment on lines
+61
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent 'id' field from being modified during update When updating a book, if the request data contains an To prevent this, remove the def update_book_by_id(book_id):
data = request.get_json()
if not data:
return jsonify({"error": "No data provided."}), 400
validation_error = validate_book(data)
if validation_error:
return jsonify(validation_error), validation_error[1]
for book in books:
if book['id'] == book_id:
+ data.pop('id', None) # Remove 'id' if present in data
book.update(data)
return jsonify(book), 200📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| return jsonify({"error": "Book not found"}), 404 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.route('/books/<string:book_id>', methods=['DELETE']) | ||||||||||||||||||||
| def delete_book_by_id(book_id): | ||||||||||||||||||||
| for book in books: | ||||||||||||||||||||
| if book['id'] == book_id: | ||||||||||||||||||||
| books.remove(book) | ||||||||||||||||||||
| return jsonify({"message": "Book deleted"}), 200 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return jsonify({"error": "Book not found"}), 404 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.errorhandler(404) | ||||||||||||||||||||
| def not_found(error): | ||||||||||||||||||||
| return jsonify({"error": "Resource not found"}), 404 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.errorhandler(400) | ||||||||||||||||||||
| def bad_request(error): | ||||||||||||||||||||
| return jsonify({"error": "Bad Request", "message": str(error)}), 400 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @book_routes.errorhandler(500) | ||||||||||||||||||||
| def internal_error(error): | ||||||||||||||||||||
| return jsonify({"error": "An internal error occurred"}), 500 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from datetime import datetime | ||
|
|
||
|
|
||
| # Validate the book data | ||
| def validate_book(data): | ||
| required_fields = ['title', 'author', 'price', 'category', 'publication_year'] | ||
| for field in required_fields: | ||
| if field not in data: | ||
| return {"error": f"'{field}' is a required field."}, 400 | ||
|
|
||
| if not isinstance(data['title'], str): | ||
| return {"error": "'title' must be a string."}, 400 | ||
|
|
||
| if not isinstance(data['author'], str): | ||
| return {"error": "'author' must be a string."}, 400 | ||
|
|
||
| if not isinstance(data['price'], int): | ||
| return {"error": "'price' must be an integer."}, 400 | ||
|
|
||
| if not isinstance(data['category'], list) or not all(isinstance(cat, str) for cat in data['category']): | ||
| return {"error": "'category' must be an array of strings."}, 400 | ||
|
|
||
| try: | ||
| pub_year = int(data['publication_year']) | ||
| if pub_year > datetime.now().year: | ||
| return {"error": "'publication_year' cannot be in the future."}, 400 | ||
| except ValueError: | ||
| return {"error": "'publication_year' must be a valid year (integer)."}, 400 | ||
|
|
||
| return None |
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.
🛠️ Refactor suggestion
Consider optimizing file copying.
While copying the entire current directory is simple, it might include unnecessary files (e.g.,
.git,__pycache__, temporary files) in the container. This can increase the image size and potentially expose sensitive information.Consider using a
.dockerignorefile to exclude unnecessary files and directories. Also, you could copy only the essential files explicitly. For example:This approach keeps the container lean and reduces potential security risks.