service.py
This file centralizes core application services for managing users, posts, categories, tags, and comments. It orchestrates data operations and business logic for a content management system, ensuring data integrity and user interactions.
Scope
-
Manages user authentication, creation, and profile updates, including password hashing and email verification.
-
Orchestrates content creation, retrieval, updating, and soft deletion for posts, categories, and tags.
-
Facilitates comment management, including creation, retrieval, updates, soft deletion, and approval processes.
-
Ensures data integrity by applying soft deletion policies and generating unique slugs for content entities.
-
Provides search and filtering capabilities for posts based on various criteria like category, tag, and keywords.
Imports
-
Session from sqlalchemy.ormImports the
Sessionclass from SQLAlchemy ORM, providing a transactional scope for database operations within service methods. -
and_, or_ from sqlalchemyImports
and_andor_operators from SQLAlchemy, enabling complex conditional filtering in database queries for entity retrieval. -
User, Post, Comment, Category, Tag from app.models.modelsImports SQLAlchemy ORM models representing core domain entities, facilitating object-relational mapping and database interaction for all services.
-
UserCreate, PostCreate, PostUpdate, CommentCreate, CommentUpdate, CategoryCreate, TagCreate from app.schemas.schemasImports Pydantic schemas for data validation and serialization of incoming request payloads and outgoing responses, ensuring data consistency and type safety.
-
hash_password, verify_password from app.core.securityImports utility functions for securely hashing and verifying user passwords, crucial for authentication and maintaining user account security.
-
generate_slug from app.utils.helpersImports a helper function to create URL-friendly slugs from strings, ensuring unique and readable identifiers for posts, categories, and tags.
-
logger from app.core.loggingImports the application's centralized logger, enabling structured logging of events, errors, and operational information across all service operations.
-
datetime from datetimeImports the
datetimemodule for handling date and time operations, primarily used for timestamping entity creation, updates, and soft deletions.
Classes
UserService
Provides static utility methods for managing user-related operations within the application. Handles user creation, retrieval, authentication, password updates, and email verification. Integrates with database sessions and user models to ensure data consistency and security.
Methods
authenticate_user
public
Authenticates a user by verifying their email and password against stored credentials. This method is crucial for secure access control and user session management throughout the application. It returns the `User` object upon successful validation.
def authenticate_user(db: Session, email: str, password: str) -> User:
-
db(Session)The SQLAlchemy database session object used for database operations. Provides the necessary connection to query user data from the database. Ensures transactional integrity and proper resource management during authentication attempts.
-
email(str)The user's email address provided for authentication. This string is used to retrieve the corresponding user record from the database. It must exactly match a registered user's email for successful lookup.
-
password(str)The plain-text password provided by the user for authentication. This string is securely verified against the stored hashed password. It is never stored directly, only used for comparison.
authenticated_user(User)The
Userobject representing the authenticated user if credentials are valid. ReturnsNoneif authentication fails due to incorrect email or password. This object contains all user details for subsequent application operations.
- Retrieve a user record from the database using the provided
emailby callingUserService.get_user_by_email(db, email). - Check if the
userobject was not found or if the providedpassworddoes not match theuser.hashed_passwordusingverify_password:- If no user is found or the password verification fails:
- Return
None, indicating authentication was unsuccessful.
- Return
- If no user is found or the password verification fails:
- If a user is found and the password verification succeeds:
- Return the
userobject, indicating successful authentication.
- Return the
Usage Tips
- Always call this method within a secure context, such as an API endpoint protected by HTTPS. Ensure
emailandpasswordare handled securely to prevent interception. Avoid logging sensitive credentials directly.
Additional Notes
- This method relies on
UserService.get_user_by_emailandverify_passwordfor its core functionality. Ensure these dependencies are correctly configured and robust.verify_passwordhandles the secure comparison of hashed passwords.
create_user
public
Creates a new user record in the database by hashing the provided password and persisting user details. This method ensures data integrity and logs creation events, returning the newly created `User` object for further application use.
def create_user(db: Session, user_create: UserCreate) -> User:
-
db(Session)The SQLAlchemy database session object used for interacting with the database. This session manages transactions and persistence operations for the new user record creation process.
-
user_create(UserCreate)A Pydantic schema object containing the new user's details, including
email,username,full_name,password,bio, andprofile_image_url. This object provides validated input for user creation.
db_user(User)The newly created
Userobject, retrieved from the database after successful commit. This object includes all persisted attributes, such as theidandhashed_password, ready for subsequent operations.
ExceptionRaised if any error occurs during the user creation process, such as database connection issues or constraint violations. The transaction is rolled back, and the original exception is re-raised.
- Begin a
tryblock to handle potential exceptions during user creation. - Create a new
Userinstance nameddb_user:- Assign
user_create.emailtodb_user.email. - Assign
user_create.usernametodb_user.username. - Assign
user_create.full_nametodb_user.full_name. - Hash
user_create.passwordusinghash_password()and assign the result todb_user.hashed_password. - Assign
user_create.biotodb_user.bio. - Assign
user_create.profile_image_urltodb_user.profile_image_url.
- Assign
- Add the newly created
db_userobject to the database sessiondb. - Commit the transaction to persist the
db_userrecord to the database. - Refresh the
db_userobject from the database to ensure it contains any generated fields, likeid. - Log an informational message indicating successful user creation, including the user's email.
- Return the
db_userobject. - If an
Exceptionoccurs during thetryblock:- Roll back the database session
dbto discard any uncommitted changes. - Log an error message detailing the exception that occurred during user creation.
- Re-raise the caught
Exceptionto propagate the error to the caller.
- Roll back the database session
Usage Tips
- Ensure
UserCreateschema includes robust validation for all fields, especiallyemailandpassword, before calling this method. This prevents invalid data from reaching the database and causing errors.
Additional Notes
- The
hash_password()function is critical for security; ensure it uses a strong, up-to-date hashing algorithm. Never store plain-text passwords, always hash them before database persistence.
get_user_by_email
public
This method retrieves a `User` object from the database using their email address. It ensures the user is not marked as deleted, providing a safe way to fetch active user records. Essential for authentication flows and user profile management.
def get_user_by_email(db: Session, email: str) -> User:
-
db(Session)The SQLAlchemy
Sessionobject used to interact with the database. This session manages the connection and transactions for querying user data. It is crucial for executing database operations effectively. -
email(str)The email address string of the user to be retrieved. This parameter is used to uniquely identify the user record within the database. It must be a valid email format for successful lookup.
user_record(User)Returns a
Userobject if a matching user is found in the database. If no user matches the provided email or if the user is marked as deleted, it returnsNone. This object represents the active user.
- Initiate a database query on the
Usermodel using the provideddbsession. - Apply a filter condition to select users where
User.emailmatches the inputemail. - Add another filter condition to ensure
User.deleted_atisNone, meaning the user is not soft-deleted. - Execute the query and retrieve the first matching
Userrecord found. - Return the retrieved
Userobject, orNoneif no active user matches the criteria.
Usage Tips
-
Always use this method when fetching user details by email to ensure only active, non-deleted accounts are retrieved. This prevents accidental access or operations on logically deleted user data within the application.
-
Integrate this method into authentication services or user lookup functionalities where a unique email identifies the user. It provides a consistent and secure way to access user records efficiently.
Additional Notes
-
The method implicitly returns
Noneif no user matches the criteria, including if the user exists butdeleted_atis notNone. Handle thisNonereturn explicitly in calling code to avoidAttributeError. -
This method performs a case-sensitive email comparison by default, depending on database collation settings. If case-insensitivity is required, consider normalising email input to lowercase before calling this function.
get_user_by_id
public
This method retrieves a single user record from the database using their unique identifier. It ensures only active, non-deleted users are returned. This function is crucial for fetching specific user profiles for various application operations.
def get_user_by_id(db: Session, user_id: int) -> User:
-
db(Session)The
dbparameter represents the SQLAlchemy database session for executing queries. It provides the necessary connection and transaction context to interact with the underlying database. This session is essential for data retrieval operations. -
user_id(int)The
user_idparameter is an integer representing the unique identifier of the user to retrieve. This ID is used to precisely locate the desired user record within the database. It ensures accurate and specific user data fetching.
user(User)This method returns a
Userobject if a matching, non-deleted user is found in the database. If no such user exists, it returnsNone. This output is vital for subsequent operations requiring user data.
- Query the database for a
Userobject using the provideddbsession. - Apply a filter to select
Userrecords where theUser.idmatches the provideduser_id. - Additionally, apply a filter to ensure that the
User.deleted_atfield isNone, effectively excluding soft-deleted users. - Retrieve the first matching
Userrecord found after applying both filters.
Usage Tips
-
Always ensure the
user_idprovided is valid and corresponds to an existing user. Handle cases whereNoneis returned gracefully to prevent downstream errors in your application logic. -
Integrate this method into user profile views or administrative panels where specific user data retrieval is required. Prioritize error handling for
Nonereturns to maintain application stability.
Additional Notes
-
This method implicitly filters out soft-deleted users by checking
deleted_at == None. If you need to retrieve deleted users, a separate method or modified query would be necessary. -
The query uses
first()which efficiently retrieves only one record, optimising database load. For scenarios requiring multiple users, consider usingall()with different filtering criteria.
get_user_by_username
public
This method retrieves a single user record from the database using their unique username. It ensures only active users, not marked as deleted, are returned. This function is crucial for authentication, user profile retrieval, and various user-centric operations within the application.
def get_user_by_username(db: Session, username: str) -> User:
-
db(Session)The SQLAlchemy session object for database interaction. Provides the necessary connection and transaction management to query the
Usertable effectively. Essential for executing database operations within the application. -
username(str)The string representing the unique username of the user to retrieve. This parameter is used to filter the
Usertable, ensuring the correct user is identified for subsequent operations.
user_object(User)An instance of the
Usermodel if a matching user is found. ReturnsNoneif no user matches the provided criteria, indicating the user does not exist or is deleted.
- Query the
Usermodel using the provided SQLAlchemy database sessiondb. - Apply a filter to the query using
and_to combine two conditions. - The first condition checks if the
User.usernameattribute matches the inputusername. - The second condition checks if the
User.deleted_atattribute isNone, ensuring only non-deleted users are considered. - Execute the query and retrieve the first matching
Userobject found in the database.
Usage Tips
-
Always ensure the
usernameprovided is properly sanitised and validated before calling this method. This prevents potential SQL injection vulnerabilities and ensures data integrity during database queries. -
Handle the
Nonereturn value explicitly in your calling code. IfNoneis returned, the user does not exist or is deleted, requiring appropriate error handling or user feedback.
Additional Notes
-
This method implicitly assumes
User.usernameis unique and indexed for optimal performance. Consider adding an index to theusernamecolumn if not already present to speed up lookups. -
The
deleted_atcheck is crucial for soft-deletion strategies, preventing retrieval of logically deleted users. Ensure consistent application of this filter across all user retrieval methods.
update_password
public
This method securely updates a user's password by hashing the new password and persisting changes. It ensures data integrity through database transactions. Essential for user account management and security, it integrates with authentication systems to maintain user credentials.
def update_password(db: Session, user_id: int, new_password: str) -> bool:
-
db(Session)The
dbparameter represents the SQLAlchemy session for database operations. It provides the necessary connection to interact with the database, enabling user retrieval, password updates, and transaction management for data persistence. -
user_id(int)This integer
user_iduniquely identifies the target user whose password needs updating. It ensures the correct user record is fetched and modified, preventing unintended password changes for other accounts within the system. -
new_password(str)The
new_passwordstring contains the plain-text password provided by the user. This value is securely hashed before storage, ensuring sensitive data protection and compliance with security best practices for credential management.
password_updated(bool)This boolean indicates whether the password update operation was successful.
Truesignifies a successful update and commit, whileFalsemeans the user was not found, preventing further processing and indicating failure.
ExceptionRaised for any unexpected errors occurring during the password update process. This generic exception ensures that all unhandled issues are caught, the database transaction is rolled back, and the error is propagated for further handling.
- Begin a
tryblock to handle potential exceptions during the password update process. - Retrieve the
Userobject from the database using the provideduser_idviaUserService.get_user_by_id(). - Check if a
userobject was successfully retrieved from the database.- If
userisNone(user not found):- Return
Falseimmediately, indicating that the password update could not proceed.
- Return
- If
- If
useris found:- Hash the
new_passwordusing thehash_password()utility function. - Assign the newly hashed password to the
user.hashed_passwordattribute. - Set the
user.updated_byattribute to theuser_idof the user performing the update. - Update the
user.date_updatedattribute with the current UTC timestamp. - Commit the transaction to the database, persisting all changes to the
userrecord. - Log an informational message indicating successful password update for the user's email.
- Return
True, signifying that the password update operation completed successfully.
- Hash the
- Catch any
Exceptionthat occurs within thetryblock:- Rollback the database transaction to undo any partial changes made before the error.
- Log an error message detailing the exception that occurred during the password update.
- Re-raise the caught
Exception, propagating the error to the calling function for handling.
Usage Tips
-
Always ensure
user_idcorresponds to an existing user before calling this method. Validating user existence beforehand prevents unnecessary database operations and ensures a smoother, more efficient password update flow. -
Implement strong password policies and validation at the application layer before passing
new_passwordto this method. This enhances security by enforcing complexity requirements, protecting against weak or easily guessable passwords.
Additional Notes
-
This method relies on
datetime.utcnow()for timestamping updates. Ensure system clocks are synchronized and consider timezone implications ifdatetime.now()with timezone awareness is preferred for consistency. -
The
hash_password()function is crucial for security. Verify its implementation uses a robust, modern hashing algorithm (e.g., bcrypt, Argon2) with appropriate salt generation to protect against brute-force attacks.
verify_user_email
public
This method verifies a user's email by updating their verification status in the database. It ensures the user exists before marking them as verified. This function is crucial for user account activation workflows.
def verify_user_email(db: Session, user_id: int) -> bool:
-
db(Session)The
Sessionobject provides a transactional scope for database operations. It manages the lifecycle of database connections and ensures data consistency. This parameter is essential for interacting with the database. -
user_id(int)The unique integer identifier for the user whose email needs verification. This
user_idis used to retrieve the specific user record from the database. It ensures the correct user account is targeted for status update.
verification_status(bool)Returns
Trueif the user's email was successfully verified and updated in the database. ReturnsFalseif the specifieduser_iddoes not correspond to an existing user. This indicates operation success or failure.
ExceptionRaised for any unexpected error occurring during the email verification process. This generic exception ensures that all unhandled issues are caught, the database transaction is rolled back, and the error is logged for debugging.
- Begin a
tryblock to handle potential exceptions during the verification process. - Retrieve the
Userobject from the database usingUserService.get_user_by_id(db, user_id). - Check if the
userobject returned from the database query isNone. - If
userisNone(meaning no user was found with the givenuser_id):- Return
Falseto indicate that the verification failed because the user does not exist.
- Return
- If a
userobject is found:- Set the
user.is_verifiedattribute toTrue. - Set the
user.updated_byattribute to the provideduser_id. - Set the
user.date_updatedattribute to the current UTC time usingdatetime.utcnow(). - Commit the changes to the database session using
db.commit(). - Log an informational message using
logger.info()indicating successful email verification for the user's email. - Return
Trueto indicate successful email verification.
- Set the
- If any
Exceptionoccurs during thetryblock execution:- Enter the
except Exception as eblock. - Rollback any pending database changes using
db.rollback()to maintain data integrity. - Log an error message using
logger.error()detailing the exception that occurred during email verification. - Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
-
Integrate this method into your user registration and email confirmation workflows. Call
verify_user_email()immediately after a user clicks their unique verification link to activate their account securely and reliably. -
Ensure the
user_idpassed to this method is securely obtained and validated to prevent unauthorized verification attempts. Always verify user identity before invoking this critical status-changing operation for security.
Additional Notes
-
The method relies on
UserService.get_user_by_idto retrieve user data. Performance of this lookup directly impacts verification speed. Consider caching strategies for frequently accessed user data to optimize this process. -
This method updates
updated_byanddate_updatedfields, providing an audit trail for verification. Ensuredatetime.utcnow()is consistently used across your application for timestamping to avoid timezone issues.
CategoryService
The `CategoryService` class provides static methods for managing `Category` entities within the application's data store. It encapsulates CRUD operations like creation, retrieval, and soft deletion, ensuring data integrity and consistent access patterns for category-related business logic.
Destructor
def __del__(self):
This class does not define an explicit destructor method. Therefore, no specific cleanup or finalisation logic is executed when instances of CategoryService are garbage collected by the Python runtime environment.
Methods
create_category
public
This method creates a new category entry in the database using provided data. It ensures data integrity by rolling back transactions on error. This function is crucial for managing content organization within the application.
def create_category(db: Session, category_create: CategoryCreate, user_id: int) -> Category:
-
db(Session)The SQLAlchemy database session object used for interacting with the database. It facilitates adding, committing, and refreshing the new category instance. This session manages transaction scope and persistence.
-
category_create(CategoryCreate)A Pydantic schema object containing the data for the new category. This includes
name,slug, anddescriptionfields. It ensures structured and validated input for category creation operations. -
user_id(int)The integer ID of the user performing the category creation. This ID is used to populate the
added_byandupdated_byfields for auditing purposes. It tracks who initiated the record.
db_category(Category)The newly created and persisted
Categoryobject from the database. This object includes all fields, including database-generated IDs and timestamps. It represents the successfully added category record.
ExceptionRaised if any unexpected error occurs during the category creation process. This includes database errors or issues with data processing. The transaction is rolled back before re-raising.
- Begin a
tryblock to handle potential exceptions during category creation. - Instantiate a new
Categoryobject with data fromcategory_createanduser_id:- Set
namefromcategory_create.name. - Generate
slugusinggenerate_slugfunction withcategory_create.slug. - Set
descriptionfromcategory_create.description. - Set
added_bytouser_id. - Set
updated_bytouser_id.
- Set
- Add the newly created
db_categoryobject to the database session. - Commit the transaction to persist the new category in the database.
- Refresh the
db_categoryobject to load any database-generated fields (e.g.,id). - Log an informational message indicating successful category creation with the category's name.
- Return the
db_categoryobject. - If an
Exceptionoccurs during thetryblock:- Enter the
exceptblock, catching theExceptionase. - Roll back the database session to undo any uncommitted changes.
- Log an error message detailing the failure to create the category, including the exception string.
- Re-raise the caught
Exceptionto propagate the error further.
- Enter the
Usage Tips
-
Always ensure
category_createdata is pre-validated before calling this method to prevent common errors. Utilizegenerate_slugfor consistent, SEO-friendly URLs, enhancing content discoverability and user experience effectively. -
Wrap calls to this method within a larger transaction if multiple related database operations are involved. This ensures atomicity, preventing partial updates and maintaining data consistency across complex workflows.
Additional Notes
-
The
generate_slugfunction is crucial for creating URL-friendly identifiers, but ensure its logic handles edge cases like duplicate slugs. Consider adding unique constraint handling forslugat the database level. -
The
rollback_and_raiseerror strategy ensures data integrity by undoing changes on failure. However, calling code must handle the re-raisedExceptionappropriately, potentially translating it into a user-friendly error response.
delete_category
public
This method logically deletes a `Category` record by setting its `deleted_at` timestamp. It ensures data integrity by updating the `updated_by` field. This function is crucial for maintaining soft-deletion policies across the application.
def delete_category(db: Session, category_id: int, user_id: int) -> bool:
-
db(Session)Provides the database session for interacting with the persistence layer. It enables querying, updating, and committing changes to
Categoryrecords. Essential for transactional operations. -
category_id(int)Unique identifier for the
Categoryto be logically deleted. ThisIDis used to retrieve the specific category record from the database for modification. Essential for targeting the correct record. -
user_id(int)Identifier of the user performing the deletion. This
IDis recorded in theupdated_byfield, providing an audit trail for category modifications. Crucial for tracking administrative actions.
deletion_status(bool)Indicates whether the category was successfully marked as deleted. Returns
Trueupon successful logical deletion andFalseif the category was not found. Essential for caller to confirm operation status.
ExceptionRaised if any unexpected error occurs during the deletion process. This ensures that database transactions are rolled back, preventing partial updates and maintaining data consistency. Crucial for system stability.
- Begin a
tryblock to handle potential exceptions during the category deletion process. - Call
CategoryService.get_category_by_id(db, category_id)to retrieve theCategoryobject from the database using the providedcategory_id. - Check if the
categoryobject was found (i.e.,if not category). - If
categoryisNone(not found):- Return
False, indicating that the category could not be deleted because it does not exist.
- Return
- If
categoryis found:- Set the
category.deleted_atfield to the current UTC datetime usingdatetime.utcnow(). - Set the
category.updated_byfield to the provideduser_id. - Commit the database transaction using
db.commit()to persist the changes. - Log an informational message indicating the successful deletion of the category, including its
name. - Return
True, indicating that the category was successfully logically deleted.
- Set the
- If any
Exceptionoccurs during thetryblock:- Enter the
except Exception as e:block. - Roll back the database transaction using
db.rollback()to undo any uncommitted changes. - Log an error message detailing the exception that occurred during the category deletion.
- Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
-
Always ensure the
category_idanduser_idare valid before calling this method. Invalid IDs can lead to unnecessary database lookups or incorrect audit trails. Validate inputs carefully. -
Implement proper authorization checks before invoking this deletion method. Only users with appropriate permissions should be allowed to logically delete categories from the system. Secure access is paramount.
Additional Notes
-
This method performs a soft deletion, marking the record as deleted rather than removing it permanently. This preserves historical data and allows for potential recovery if needed. Consider data retention policies.
-
The
datetime.utcnow()function is used for consistency across different time zones. Ensure all timestamp operations within the application use UTC for reliable data management and accurate historical tracking.
get_all_categories
public
Retrieves all active `Category` objects from the database, applying pagination. This method supports fetching categories that have not been soft-deleted. It is crucial for displaying available content classifications across the application.
def get_all_categories(db: Session, skip: int = 0, limit: int = 100):
-
db(Session)The SQLAlchemy database session object used for executing queries. Provides the necessary connection and transaction context to interact with the underlying database. Essential for data retrieval operations within the application's ORM layer.
-
skip(int)An integer representing the number of records to skip for pagination. This parameter allows fetching results starting from a specific offset, enabling efficient data retrieval in large datasets. Defaults to
0for the first page. -
limit(int)An integer specifying the maximum number of
Categoryrecords to return. This parameter controls the size of the result set, preventing excessive data transfer and improving performance. Defaults to100for reasonable batch sizes.
categories(List[Category])A list of
Categorymodel instances representing active categories found in the database. Each object contains category-specific data. This collection is used for displaying, filtering, or further processing category information.
- Initiate a database query for the
Categorymodel. - Filter the
Categoryobjects where thedeleted_atattribute isNone, ensuring only active categories are retrieved. - Apply an offset to the query results based on the
skipparameter value. - Apply a limit to the number of results returned based on the
limitparameter value. - Execute the query and retrieve all matching
Categoryobjects as a list.
Usage Tips
-
Always use
skipandlimitparameters when fetching large datasets to prevent performance issues. Implement robust pagination logic in your frontend or API consumers to leverage these parameters effectively. -
Consider caching frequently accessed category lists to reduce database load and improve response times. This method provides the base data, but caching layers can significantly enhance application scalability.
Additional Notes
-
This method explicitly filters out soft-deleted categories by checking
deleted_atforNone. If deleted categories are ever needed, a separate method or an additional parameter would be required. -
The
Categorymodel is assumed to have adeleted_atfield for soft deletion. Ensure this field is properly indexed in the database for optimal query performance, especially with large category tables.
get_category_by_id
public
This method retrieves a specific `Category` object from the database using its unique identifier. It ensures only active, non-deleted categories are returned. This function is crucial for fetching category details for display or further processing.
def get_category_by_id(db: Session, category_id: int) -> Category:
-
db(Session)The SQLAlchemy database session object used to execute queries. Provides the necessary connection and transaction context for interacting with the underlying database. Essential for data retrieval operations.
-
category_id(int)The unique integer identifier of the category to be retrieved. This
idis used to precisely locate the desired category record within the database. Ensures accurate and specific data fetching.
category(Category)Returns the
Categoryobject matching the providedcategory_idif found and not deleted. If no such category exists,Noneis returned. Represents the requested category's data.
- Execute a database query to select
Categoryobjects. - Filter the
Categoryobjects whereCategory.idmatches the providedcategory_id. - Further filter the results to include only categories where
Category.deleted_atisNone, indicating an active category. - Retrieve the first matching
Categoryobject from the filtered results.
Usage Tips
-
Always check the return value for
Noneafter calling this method, as it indicates a category was not found or is soft-deleted. HandleNonegracefully to preventAttributeErrorexceptions. -
Utilise this method when you need to display category-specific content or validate relationships. It provides a direct and efficient way to access category data by its primary key.
Additional Notes
-
This method performs a soft-delete check by filtering
deleted_at == None. If hard-deleted categories are ever needed, a different query or method would be required. -
The
first()method is used, which is efficient for retrieving a single record. For scenarios requiring multiple categories, consider usingall()with appropriate filtering or pagination for better performance.
TagService
This class provides static methods for managing `Tag` entities within a database. It handles creation, retrieval, and soft deletion of tags, ensuring data integrity and logging operations. It integrates with a database session and `TagCreate` DTO.
Methods
create_tag
public
Creates a new `Tag` entry in the database with provided details. It generates a slug, assigns creation/update user, then persists the tag. This method ensures data integrity and proper logging for new tag entities.
def create_tag(db: Session, tag_create: TagCreate, user_id: int) -> Tag:
-
db(Session)The SQLAlchemy database session object used for database operations. This session manages the transaction and persistence of the new
Tagentity within the application's data store. -
tag_create(TagCreate)A Pydantic model containing the data for the new tag, including
nameandslug. This object provides the necessary attributes to construct theTagdatabase model instance for creation. -
user_id(int)The integer ID of the user initiating the tag creation. This ID is recorded as
added_byandupdated_byto track authorship and modification history for the newTagentry.
db_tag(Tag)The newly created and persisted
Tagdatabase model instance. This object includes all attributes, including any database-generated IDs, after successful commit. It represents the complete, stored tag.
ExceptionRaised for any unexpected errors occurring during the tag creation process. This generic exception ensures that database transactions are rolled back, preventing partial or corrupted data entries.
- Begin a
tryblock to handle potential exceptions during tag creation. - Instantiate a
Tagobject:- Set
nameattribute fromtag_create.name. - Generate
slugusinggenerate_slugfunction withtag_create.slug. - Set
added_byattribute touser_id. - Set
updated_byattribute touser_id.
- Set
- Add the newly created
db_tagobject to the database session. - Commit the transaction to persist the
db_tagin the database. - Refresh the
db_tagobject to load any database-generated attributes, such as its ID. - Log an informational message indicating successful tag creation, including the tag's name.
- Return the
db_tagobject. - If any
Exceptionoccurs during thetryblock:- Enter the
exceptblock catching a genericException. - Rollback the database session to undo any uncommitted changes.
- Log an error message detailing the failure to create the tag, including the exception string.
- Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
-
Ensure
tag_createdata is pre-validated before calling this method to prevent database constraint errors. Proper validation reduces unexpected exceptions and maintains data quality within the system effectively. -
Always handle the re-raised
Exceptionin the calling context to provide appropriate user feedback or retry mechanisms. This ensures robust error management and graceful degradation of application functionality.
Additional Notes
-
The
generate_slugfunction is crucial for creating SEO-friendly and unique identifiers for tags. Its behavior regarding duplicate slugs or special characters should be well-understood for consistent tag management. -
The
db.rollback()ensures atomicity; if any part of the transaction fails, no partial data is committed. This is vital for maintaining database consistency, especially in concurrent environments.
delete_tag
public
This method logically deletes a specific tag by setting its `deleted_at` timestamp. It marks records as inactive, ensuring data integrity without permanent removal. This supports audit trails and potential recovery of deleted tags.
def delete_tag(db: Session, tag_id: int, user_id: int) -> bool:
-
db(Session)This
Sessionobject provides the database connection and transaction management. It facilitates querying, updating, and committing changes to the underlying data store, ensuring data consistency and persistence for all database operations. -
tag_id(int)The unique integer identifier for the tag intended for deletion. This
tag_idis crucial for locating the specificTagrecord within the database to apply the logical deletion flag effectively. -
user_id(int)The unique integer identifier of the user performing the deletion. This
user_idis recorded in theupdated_byfield, providing an audit trail of who initiated the logical deletion action.
deletion_status(bool)A boolean value indicating the success or failure of the tag deletion operation. Returns
Trueif the tag was successfully marked as deleted,Falseif the tag was not found in the system.
ExceptionRaised for any unexpected errors during the tag deletion process. This generic exception ensures all unhandled issues are caught, logged, and re-raised, preventing silent failures and maintaining application stability.
- Begin a
tryblock to handle potential exceptions during the tag deletion process. - Retrieve the
Tagobject from the database usingTagService.get_tag_by_id()with the provideddbsession andtag_id. - Check if the
tagobject was found (i.e.,tagis notNone):- If
tagisNone(the tag was not found in the database):- Return
Falseto indicate that the tag could not be deleted because it does not exist.
- Return
- If
- If the
tagobject exists:- Set the
deleted_atattribute of thetagobject to the current UTC timestamp usingdatetime.utcnow(). - Set the
updated_byattribute of thetagobject to the provideduser_id. - Commit the pending changes to the database using
db.commit(), persisting the logical deletion. - Log an informational message indicating the successful deletion of the tag, including its name.
- Return
Trueto signify that the tag was successfully logically deleted.
- Set the
- If any
Exceptionoccurs during thetryblock:- Enter the
exceptblock, catching theExceptionase. - Rollback any pending database changes using
db.rollback()to ensure data consistency. - Log an error message detailing the failure to delete the tag, including the exception string.
- Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
- Always verify
tag_idanduser_idbefore calling this method to prevent unnecessary database lookups. Implement robust error handling in calling code to manage re-raised exceptions gracefully and inform users promptly.
Additional Notes
- This method performs a logical deletion by updating a timestamp, not a physical removal from the database. This allows for data recovery and maintains historical integrity, but requires filtering logically deleted records in all queries.
get_all_tags
public
Retrieves a paginated list of all active `Tag` objects from the database. This method is crucial for displaying available tags across the application, supporting content categorization and discovery features effectively.
def get_all_tags(db: Session, skip: int = 0, limit: int = 100):
-
db(Session)The SQLAlchemy database session object used for executing queries. This session provides the necessary connection and transaction management to interact with the underlying database effectively.
-
skip(int)The number of
Tagrecords to skip from the beginning of the query result. This parameter facilitates pagination, allowing retrieval of subsequent data pages efficiently for display purposes. -
limit(int)The maximum number of
Tagrecords to retrieve in a single query. This parameter controls the size of the result set, enabling efficient data fetching for paginated displays or batch processing.
tags(List[Tag])A list of
Tagobjects that are not marked as deleted. EachTagobject represents an active category or keyword, suitable for display or further processing within the application.
- Query the database for
Tagobjects using the provideddbsession. - Filter the
Tagobjects to include only those where thedeleted_atfield isNone, ensuring only active tags are considered. - Apply an
offsetto the filtered query results based on theskipparameter, skipping a specified number of records. - Apply a
limitto the offset results based on thelimitparameter, restricting the number of records returned. - Execute the constructed query and retrieve all matching
Tagobjects as a list.
Usage Tips
- Always use appropriate
skipandlimitvalues to prevent fetching excessive data, which can impact performance. Implement client-side pagination controls to leverage these parameters effectively.
Additional Notes
- This method implicitly assumes
Tagobjects with adeleted_attimestamp are soft-deleted. Ensure consistent application of this soft-delete pattern across all tag management operations for data integrity.
get_tag_by_id
public
Retrieves a specific `Tag` object from the database using its unique identifier. This method ensures only active, non-deleted tags are returned. It is crucial for fetching tag details for display or association within the application.
def get_tag_by_id(db: Session, tag_id: int) -> Tag:
-
db(Session)The SQLAlchemy
Sessionobject used to interact with the database. Provides the necessary connection and transaction context for executing queries. Essential for data retrieval operations within the application's data layer. -
tag_id(int)The unique integer identifier of the
Tagto be retrieved. Thisidparameter is used to precisely locate the desired tag record in the database. Ensures accurate and specific data fetching.
tag_object(Tag)Returns the
Tagobject matching the providedtag_idif found and not deleted. If no such tag exists or it is marked as deleted,Noneis returned. Represents the requested tag's data.
- Initiate a database query on the
Tagmodel using the provideddbsession. - Apply a filter to select
Tagrecords where theidcolumn matches thetag_idparameter. - Further apply a filter to ensure that the
deleted_atcolumn of theTagrecord isNone, indicating it is not soft-deleted. - Execute the query and retrieve the first matching
Tagobject found, orNoneif no such record exists.
Usage Tips
-
Always check the return value for
Noneafter callingget_tag_by_id()to handle cases where the tag does not exist or is soft-deleted. This preventsAttributeErrorwhen accessing tag properties. -
Utilize this method when you need to fetch a single, specific tag by its identifier for operations like displaying tag details or associating it with other entities.
Additional Notes
-
This method performs a soft-delete check, meaning it will not return tags that have a value in their
deleted_atfield. This ensures data consistency and integrity across the application. -
The
first()method efficiently retrieves only one record, optimising database performance for single-item lookups. Consider usingall()if multiple tags matching criteria were expected, though not applicable here.
PostService
PostService provides comprehensive static methods for managing blog posts within the application. It handles creation, retrieval, updating, and soft deletion of `Post` entities, ensuring data integrity and efficient database interactions for content management operations.
Methods
create_post
public
This method creates a new `Post` entry in the database, associating it with a specific user and optional tags. It handles data persistence, slug generation, and error logging for robust content management operations within the application.
def create_post(db: Session, post_create: PostCreate, user_id: int) -> Post:
-
db(Session)The SQLAlchemy database session instance used for all database operations. This session manages the transaction, allowing for adding, committing, and rolling back changes to ensure data integrity during post creation.
-
post_create(PostCreate)A Pydantic schema object containing the data for the new post, including
title,content,category_id, and optionaltag_ids. This object ensures structured and validated input for post creation. -
user_id(int)The integer ID of the user who is authoring and adding this post. This
user_idis used to establish theauthor_idandadded_by/updated_byrelationships for the newPostobject.
db_post(Post)The newly created and persisted
Postobject, including its generatedidand all associated data. This object represents the complete and saved post record from the database after successful creation.
ExceptionRaised for any unhandled error occurring during the post creation process, including database transaction failures or issues with tag association. The transaction is rolled back to maintain data consistency.
- Begin a
tryblock to handle potential exceptions during the post creation process. - Instantiate a new
Postobject,db_post, populating its fields from thepost_createdata and the provideduser_id. - Generate a URL-friendly
slugfrompost_create.slugusing thegenerate_slug()helper function for the new post. - Assign the
user_idto theauthor_id,added_by, andupdated_byfields of the newly createddb_post. - Check if the
post_create.tag_idslist is provided and contains any tag identifiers.- If
tag_idsare present:- Query the database for
Tagobjects whoseids match those inpost_create.tag_ids. - Assign the retrieved
Tagobjects to thetagsrelationship of thedb_postobject.
- Query the database for
- If
- Add the newly constructed
db_postobject to the SQLAlchemy database session. - Commit the current transaction to persist the
db_postand its associated tags to the database. - Refresh the
db_postobject to load any database-generated attributes, such as its primary keyid. - Log an informational message using
logger.infoindicating the successful creation of the post, including itstitle. - Return the fully populated and refreshed
db_postobject, representing the newly created database record. - If any
Exceptionoccurs during thetryblock execution:- Enter the
exceptblock to handle the encountered error. - Rollback the database session to discard any uncommitted changes and maintain data integrity.
- Log an error message using
logger.errordetailing the exception that occurred during post creation. - Re-raise the caught
Exceptionto propagate the error further up the call stack for higher-level handling.
- Enter the
Usage Tips
-
Ensure
post_createdata is thoroughly validated before calling this method to prevent database integrity issues. Pre-validation reduces unexpected errors and improves data quality for all newPostentries. -
Always handle the re-raised
Exceptionin the calling context to provide appropriate user feedback or retry mechanisms. This ensures robust error management and graceful degradation for post creation failures.
Additional Notes
-
The
generate_slug()function is crucial for SEO-friendly URLs, ensuring unique and readable identifiers for each post. Its proper functioning is vital for content discoverability and user experience. -
The
db.rollback()call within the exception block is essential for transactional integrity, preventing partial data writes. This guarantees that either the entire post creation succeeds or completely fails.
delete_post
public
This method logically deletes a specific post by setting its `deleted_at` timestamp. It ensures data integrity by marking the post as deleted rather than physically removing it. This approach supports audit trails and potential recovery mechanisms effectively.
def delete_post(db: Session, post_id: int, user_id: int) -> bool:
-
db(Session)The SQLAlchemy database session object used for database operations. It provides the connection and transaction management for interacting with the database. This session is crucial for fetching and updating the
Postrecord. -
post_id(int)The unique identifier of the post to be logically deleted. This integer value precisely targets the specific
Postentry within the database for modification. It ensures the correct post is processed. -
user_id(int)The unique identifier of the user performing the deletion. This
user_idis recorded in theupdated_byfield, providing an audit trail of who initiated the logical deletion. It tracks accountability.
deletion_status(bool)A boolean indicating whether the post was successfully marked as deleted. Returns
Trueif the post was found and updated,Falseif the post was not found. This status informs the caller of the operation's outcome.
ExceptionRaised if any unexpected error occurs during the post deletion process. This generic exception ensures that all unhandled issues are caught, the database transaction is rolled back, and the error is propagated.
- Begin a
tryblock to handle potential exceptions during the deletion process. - Attempt to retrieve the
Postobject from the database usingPostService.get_post_by_idwith the provideddbsession andpost_id. - Check if
db_post(the retrieved post) isNone:- If
db_postisNone(post not found):- Return
Falseto indicate that the post could not be deleted because it does not exist.
- Return
- If
- If
db_postis found:- Set the
deleted_atattribute ofdb_postto the current UTC datetime usingdatetime.utcnow(). - Set the
updated_byattribute ofdb_postto the provideduser_id. - Commit the changes to the database session
dbto persist the logical deletion. - Log an informational message indicating successful post deletion, including the post's title.
- Return
Trueto indicate that the post was successfully marked as deleted.
- Set the
- If any
Exceptionoccurs during thetryblock:- Enter the
exceptblock, catching theExceptionase. - Rollback the database session
dbto undo any uncommitted changes. - Log an error message indicating the failure to delete the post, including the exception string.
- Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
- Always verify the
post_idanduser_idbefore calling this method to prevent unnecessary database lookups. Implement robust authorization checks to ensure only authorized users can initiate post deletions.
Additional Notes
- This method performs a soft delete, preserving the post record for auditing or potential restoration. Consider implementing a separate hard delete mechanism for permanent data removal, if required by data retention policies.
get_all_posts
public
This method retrieves a paginated list of `Post` objects from the database. It allows filtering by category, tag, and publication status. This function is crucial for displaying blog posts on various pages, supporting content categorisation and user browsing experiences effectively.
def get_all_posts(db: Session, skip: int = 0, limit: int = 10, category_id: int = None, tag_id: int = None, published_only: bool = True):
-
db(Session)The SQLAlchemy database
Sessioninstance used for executing queries. This session manages the database connection and transaction state, ensuring data consistency and proper resource handling for all operations. -
skip(int)The number of records to skip from the beginning of the query result set. This parameter facilitates pagination, allowing retrieval of subsequent data pages efficiently. Defaulting to
0ensures the first page is returned. -
limit(int)The maximum number of
Postrecords to return in a single query. This parameter controls the size of each paginated result set, preventing excessive data transfer and improving application performance. Defaulting to10provides a reasonable page size. -
category_id(int)An optional integer ID to filter posts belonging to a specific category. When provided, only posts associated with this
category_idwill be included in the results, enabling category-specific content display. -
tag_id(int)An optional integer ID to filter posts associated with a particular tag. If supplied, the query will join with the
Tagmodel to include only posts linked to thistag_id, supporting tag-based content filtering. -
published_only(bool)A boolean flag indicating whether to retrieve only published posts. When
True, only posts marked asis_publishedwill be returned, ensuring only public content is displayed by default.
posts(List[Post])A list of
Postobjects matching the specified filtering and pagination criteria. EachPostobject represents a blog entry with its associated data, ready for display or further processing within the application.
- Initialize a database query for
Postobjects using the provideddbsession. - Filter the initial query to include only posts where
Post.deleted_atisNone, ensuring only active posts are considered. - Check if
published_onlyisTrue:- If
True, further filter the query to include only posts wherePost.is_publishedisTrue.
- If
- Check if
category_idis provided (notNone):- If
category_idexists, filter the query to include posts wherePost.category_idmatches the providedcategory_id.
- If
- Check if
tag_idis provided (notNone):- If
tag_idexists, perform an inner join withPost.tagsand filter to include posts whereTag.idmatches the providedtag_id.
- If
- Order the filtered query results by
Post.date_addedin descending order, ensuring the most recent posts appear first. - Apply pagination by offsetting the results by
skiprecords. - Limit the number of returned results to
limitrecords. - Execute the constructed query and retrieve all matching
Postobjects as a list.
Usage Tips
-
Utilise
skipandlimitparameters for efficient pagination when displaying large numbers of posts. This prevents loading all records into memory, significantly improving performance and user experience for content browsing. -
Combine
category_idandtag_idfilters to create highly specific content views. This allows users to narrow down their search results effectively, enhancing content discoverability and relevance within the application.
Additional Notes
-
The
published_onlyfilter defaults toTrue, meaning unpublished posts are excluded unless explicitly set toFalse. Remember to adjust this parameter when retrieving posts for administrative or draft viewing purposes. -
Filtering by
tag_idinvolves a database join operation, which might have performance implications for very large datasets. Consider optimising database indexes onPost.tagsandTag.idfor improved query speed.
get_post_by_id
public
Retrieves a single `Post` object from the database using its unique identifier. This method ensures only active, non-deleted posts are returned. It is crucial for fetching specific post details for display or further processing.
def get_post_by_id(db: Session, post_id: int) -> Post:
-
db(Session)The SQLAlchemy database session object used to interact with the database. This session facilitates querying and retrieving
Postentities, ensuring proper transaction management and connection handling for data operations. -
post_id(int)The unique integer identifier of the
Postto be retrieved from the database. Thisidis essential for precisely locating the desired post record, ensuring accurate and efficient data fetching.
post(Post)The
Postobject matching the providedpost_idand not marked as deleted. ReturnsNoneif no such post is found in the database. This object contains all relevant post data.
- Initiate a database query targeting the
Postmodel. - Apply a filter to select
Postrecords where thePost.idcolumn matches the providedpost_id. - Further apply a filter to ensure the
Post.deleted_atcolumn isNone, indicating the post is not soft-deleted. - Execute the query and retrieve the first matching
Postobject found, orNoneif no such post exists.
Usage Tips
-
Always ensure the
post_idprovided is valid and corresponds to an existing post. This method implicitly handles soft-deleted posts, returningNonefor them, so check the return value carefully. -
Integrate this method into API endpoints or service layers requiring specific post data. It provides a robust way to fetch individual posts while respecting deletion status, simplifying data retrieval logic significantly.
Additional Notes
-
This method performs a combined filter on
idanddeleted_atfor efficiency. For performance-critical applications, consider adding an index ondeleted_atif not already present, alongside the primary key. -
The
Postobject returned is an ORM instance. Accessing its attributes might trigger lazy loading of relationships, potentially leading to N+1 query issues if not handled withjoinedloadorselectinloadin broader contexts.
get_post_by_slug
public
Retrieves a single `Post` object from the database using its unique slug. This method ensures only non-deleted posts are returned, facilitating content display and management across the application's public interfaces.
def get_post_by_slug(db: Session, slug: str) -> Post:
-
db(Session)The SQLAlchemy database session instance used for executing queries. This session provides the necessary connection and transaction management to interact with the underlying database effectively.
-
slug(str)The unique string identifier for the
Postto be retrieved. Thisslugis typically a URL-friendly version of the post's title, ensuring direct access to specific content.
post(Post)The
Postobject matching the providedslugand not marked as deleted. ReturnsNoneif no such post is found, indicating the content is unavailable or does not exist.
- Query the database for
Postobjects. - Filter the
Postobjects by two conditions:- The
slugattribute of thePostmust match the providedslugparameter. - The
deleted_atattribute of thePostmust beNone, ensuring only active posts are considered.
- The
- Retrieve the first
Postobject that satisfies both filtering conditions. - Return the found
Postobject, orNoneif no matching post exists.
Usage Tips
- Always ensure
slugvalues are unique and properly indexed in your database for optimal query performance. This method relies onslugfor efficient retrieval, making indexing crucial for large datasets.
Additional Notes
- This method implicitly handles soft-deleted posts by filtering
deleted_at == None. If you need to retrieve deleted posts, a separate method or modified query would be necessary to bypass this specific condition.
get_user_posts
public
Retrieves a paginated list of posts authored by a specific user, excluding soft-deleted entries. This method efficiently queries the database, filtering by author ID and active status, then orders results for consistent display across the application.
def get_user_posts(db: Session, user_id: int, skip: int = 0, limit: int = 10):
-
db(Session)The SQLAlchemy
Sessionobject used for database interaction. This session manages the connection and transactions, enabling efficient querying and retrieval ofPostentities from the underlying data store. -
user_id(int)The unique integer identifier of the user whose posts are to be retrieved. This
user_idis crucial for filtering posts, ensuring only relevant content associated with the specified author is returned. -
skip(int)The number of initial posts to skip for pagination purposes. This integer parameter allows fetching results starting from a specific offset, commonly used in conjunction with
limitfor paginated data display. -
limit(int)The maximum number of posts to return in the result set. This integer parameter controls the size of the fetched data chunk, essential for efficient pagination and preventing excessively large query results.
posts_list(list[Post])A list of
Postobjects matching the criteria, ordered bydate_addeddescending. EachPostobject represents an active entry authored by the specified user, ready for display or further processing.
- Query the database for
Postentities. - Filter the
Postentities wherePost.author_idmatches the provideduser_id. - Further filter the
Postentities to include only those wherePost.deleted_atisNone, ensuring only active posts are retrieved. - Order the filtered
Postentities byPost.date_addedin descending order. - Apply an offset of
skipto the ordered results for pagination. - Limit the number of results to
limitfor pagination. - Execute the query and retrieve all matching
Postobjects as a list. - Return the list of
Postobjects.
Usage Tips
-
Utilize
skipandlimitparameters to implement efficient pagination for user post listings. This prevents loading excessive data, improving application performance and user experience, especially for users with many posts. -
Ensure
user_idcorresponds to an existing user to avoid empty results. While this method does not validateuser_idexistence, upstream validation is recommended for robust data retrieval operations.
Additional Notes
-
The
Post.deleted_at == Nonecondition effectively implements soft deletion, ensuring logically deleted posts are excluded from results without permanent data removal. This preserves historical data while maintaining clean active listings. -
This method assumes
Postobjects haveauthor_idanddate_addedfields, and adeleted_atfield for soft deletion. Ensure yourPostmodel adheres to this structure for correct query execution.
increment_view_count
public
Increments the view count for a specified post in the database. This method ensures that post engagement metrics are accurately updated. It is crucial for tracking content popularity and user interest effectively.
def increment_view_count(db: Session, post_id: int):
-
db(Session)The SQLAlchemy database session object used for database operations. This session manages transactions and interactions with the underlying database. It is essential for data persistence and retrieval.
-
post_id(int)The unique identifier of the post whose view count needs to be incremented. This integer value precisely targets the specific post record within the database. It ensures correct post identification.
- No Return Value
This method does not return any explicit value upon completion. Its primary purpose is to perform a side effect by updating the
view_countin the database. Callers should not expect a return.
- Attempt to execute the following block for database operations:
- Retrieve the post from the database using
PostService.get_post_by_id()with the provideddbsession andpost_id. - Check if a post (
db_post) was successfully retrieved (i.e.,db_postis notNone):- If
db_postexists:- Increment the
view_countattribute of thedb_postobject by one. - Commit the changes to the database using
db.commit()to persist the updatedview_count.
- Increment the
- If
- Retrieve the post from the database using
- If any
Exceptionoccurs during the execution of thetryblock:- Log an error message using
logger.error(), including a descriptive string indicating the failure to increment the view count and the specific exception details.
- Log an error message using
Usage Tips
- Call this method whenever a post is successfully viewed by a user to maintain accurate engagement metrics. Ensure
post_idis valid and corresponds to an existing post for successful updates. Handle potential network or database issues gracefully.
Additional Notes
- The method uses a
try-exceptblock to catch and log any exceptions during the process, preventing crashes. However, it does not re-raise, potentially masking issues. Consider adding more specific error handling or re-raising for critical failures.
search_posts
public
This method efficiently searches for published blog posts within the database based on a provided search query. It filters by title, content, or excerpt, ensuring only active, visible posts are returned. Ideal for implementing search functionality across the application.
def search_posts(db: Session, search_query: str, skip: int = 0, limit: int = 10):
-
db(Session)The SQLAlchemy session object used to interact with the database. This session provides the necessary connection and transaction management for executing queries. It ensures data consistency and proper resource handling during database operations.
-
search_query(str)The string containing keywords to search for within post titles, content, or excerpts. This parameter drives the core search functionality, allowing users to find relevant posts. It supports case-insensitive matching for broader results.
-
skip(int)The number of records to skip from the beginning of the result set. This parameter is crucial for implementing pagination, allowing clients to retrieve subsequent pages of search results. Defaults to
0for the first page. -
limit(int)The maximum number of records to return in a single query result. This parameter controls the page size for pagination, preventing excessively large data transfers. Defaults to
10to provide manageable result sets.
posts(List[Post])A list of
Postmodel instances matching the search criteria. EachPostobject represents a published, non-deleted blog entry from the database. This collection forms the paginated search results displayed to users.
- Initiate a database query on the
Postmodel using the provideddbsession. - Apply a filter condition to the query, combining multiple criteria using
and_:- Ensure
Post.deleted_atisNone, meaning the post has not been soft-deleted. - Ensure
Post.is_publishedisTrue, meaning the post is publicly visible. - Apply an
or_condition to search across multiplePostfields:- Check if
Post.titlecontains thesearch_query(case-insensitive usingilike). - Check if
Post.contentcontains thesearch_query(case-insensitive usingilike). - Check if
Post.excerptcontains thesearch_query(case-insensitive usingilike).
- Check if
- Ensure
- Order the filtered results by
Post.date_addedin descending order, showing newer posts first. - Apply an
offsetto skip a specified number of initial results for pagination. - Apply a
limitto restrict the total number of results returned for the current page. - Execute the constructed query using
.all()to fetch all matchingPostobjects from the database.
Usage Tips
-
Always sanitize
search_queryinput to prevent SQL injection vulnerabilities, even thoughilikehelps. Implement robust input validation before passing user-provided strings to this method for secure database interactions. -
For performance-critical applications with large datasets, consider adding full-text search indexing to
Post.title,Post.content, andPost.excerpt. This significantly speeds up complex text searches compared toilikeoperations.
Additional Notes
-
This method implicitly assumes
Postmodel hasdeleted_at,is_published,title,content,excerpt, anddate_addedattributes. Ensure yourPostmodel definition includes these fields for correct query execution. -
The
ilikeoperator is database-specific and might behave differently across various SQL dialects. Verify its exact behavior with your chosen database system to ensure consistent case-insensitive search results.
update_post
public
This method updates an existing blog post's details in the database. It applies changes from `post_update` to the `Post` object, handles slug generation, and manages tag associations. Essential for content management, ensuring data consistency and audit trails.
def update_post(db: Session, post_id: int, post_update: PostUpdate, user_id: int) -> Post:
-
db(Session)The SQLAlchemy database session object used for all database operations. Provides the necessary connection and transaction management for fetching and persisting post data effectively.
-
post_id(int)The unique integer identifier of the post to be updated. This
idis crucial for locating the specificPostrecord within the database for modification. Ensures correct post targeting. -
post_update(PostUpdate)An object containing the fields to be updated for the post. This
Pydanticmodel specifies which attributes can be modified, allowing partial updates and ensuring data integrity during the update process. -
user_id(int)The unique integer identifier of the user performing the update operation. This
idis recorded in theupdated_byfield, providing an audit trail of who last modified the post.
-
updated_post(Post)The
Postobject after successful updates have been applied and committed to the database. This returned object reflects all modifications, includingdate_updatedandupdated_byfields, for immediate use. -
No Return Value
Returns
Noneif the specifiedpost_iddoes not correspond to an existing post in the database. This indicates that no post was found for the given identifier, preventing further update attempts.
ExceptionRaised for any unexpected errors occurring during the post update process. This generic exception catches unforeseen issues, rolls back the transaction, logs the error, and re-raises for upstream handling.
- Attempt to execute the post update operation within a
tryblock.- Retrieve the
Postobject from the database usingpost_idby callingPostService.get_post_by_id(). - Check if the retrieved
db_postobject isNone(indicating the post was not found).- If
db_postisNone, returnNoneimmediately.
- If
- If
post_update.titleis provided, updatedb_post.titlewith the new title. - If
post_update.slugis provided, generate a new slug usinggenerate_slug()and updatedb_post.slug. - If
post_update.contentis provided, updatedb_post.contentwith the new content. - If
post_update.excerptis notNone, updatedb_post.excerptwith the new excerpt. - If
post_update.category_idis notNone, updatedb_post.category_idwith the new category ID. - If
post_update.is_publishedis notNone, updatedb_post.is_publishedwith the new publication status. - If
post_update.featured_image_urlis notNone, updatedb_post.featured_image_urlwith the new URL. - If
post_update.tag_idsis notNone:- Query the database for
Tagobjects whoseids are present inpost_update.tag_ids. - Assign the retrieved list of
tagstodb_post.tags, replacing existing associations.
- Query the database for
- Set
db_post.updated_byto the provideduser_idfor auditing purposes. - Set
db_post.date_updatedto the current UTC datetime usingdatetime.utcnow(). - Commit the transaction to persist all changes to the database using
db.commit(). - Refresh the
db_postobject to load any updated data from the database usingdb.refresh(). - Log an informational message indicating the successful update of the post, including its title.
- Return the updated
db_postobject.
- Retrieve the
- Catch any
Exceptionthat occurs during the execution of thetryblock.- Rollback the database transaction to undo any partial changes using
db.rollback(). - Log an error message detailing the exception that occurred during the post update.
- Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Rollback the database transaction to undo any partial changes using
Usage Tips
- Ensure
post_updateonly contains fields intended for modification to prevent unintended data changes. Always validatepost_idanduser_idagainst existing records before calling this method for robust security.
Additional Notes
-
The method uses
datetime.utcnow()fordate_updated, ensuring timezone-agnostic timestamps. This is crucial for applications serving users across different time zones, maintaining consistent chronological order for updates. -
Tag association is handled by replacing the entire
tagscollection. If partial tag updates are needed, consider a separate method or more granular logic to add/remove specific tags efficiently.
CommentService
Provides static utility methods for managing `Comment` entities within the application's data store. Facilitates creation, retrieval, update, and deletion operations for comments, ensuring data integrity and consistent access patterns. Integrates with a database session for persistent storage.
Methods
approve_comment
public
This method approves a specific comment by setting its approval status to `True`. It updates the `updated_by` field and commits changes to the database. Essential for moderation workflows, ensuring only verified content is visible to users.
def approve_comment(db: Session, comment_id: int, user_id: int) -> bool:
-
db(Session)The SQLAlchemy database session object used for database operations. Provides the connection and transaction management necessary to fetch and update the comment record effectively within the application context.
-
comment_id(int)The unique integer identifier of the comment to be approved. This
idis crucial for locating the specificCommentrecord in the database for status modification. Ensures correct comment targeting. -
user_id(int)The unique integer identifier of the user performing the approval action. This
user_idis recorded in theupdated_byfield, providing an audit trail for moderation actions. Essential for accountability.
approval_status(bool)A boolean indicating whether the comment approval operation was successful. Returns
Trueif the comment was found and approved,Falseif the comment was not found. Useful for UI feedback.
ExceptionRaised for any unexpected errors occurring during the comment approval process. This generic exception ensures that all unhandled issues are propagated, allowing higher-level error handling to manage system failures.
- Begin a
tryblock to handle potential database operation errors. - Retrieve the
Commentobject from the database usingCommentService.get_comment_by_id()with the provideddbsession andcomment_id. - Check if the
db_commentobject was successfully retrieved (i.e., notNone).- If
db_commentisNone(comment not found):- Return
Falseto indicate that the approval failed because the comment does not exist.
- Return
- If
- If
db_commentis found:- Set the
is_approvedattribute of thedb_commentobject toTrue. - Set the
updated_byattribute of thedb_commentobject to the provideduser_id. - Commit the changes to the database using
db.commit(). - Log an informational message indicating the successful approval of the comment using
logger.info(). - Return
Trueto indicate that the comment was successfully approved.
- Set the
- If any
Exceptionoccurs during thetryblock:- Enter the
except Exception as eblock. - Rollback any pending database changes using
db.rollback()to maintain data consistency. - Log an error message detailing the exception using
logger.error(). - Re-raise the caught
Exceptionto propagate the error further up the call stack.
- Enter the
Usage Tips
-
Ensure
comment_idanduser_idare valid integers corresponding to existing records. Calling this method with non-existent IDs will result inFalseor an exception. Always validate inputs beforehand for robustness. -
Integrate this method into a secure moderation panel, ensuring only authorized users can approve comments. Implement proper access control checks before invoking
approve_comment()to prevent unauthorized content manipulation.
Additional Notes
-
The
CommentService.get_comment_by_id()call implies a dependency on a service layer for data access. This abstraction helps maintain separation of concerns and testability within the application architecture. -
This method performs a database transaction with
db.commit()anddb.rollback(). This ensures atomicity; either all changes are saved, or none are, preventing partial updates and maintaining data integrity.
create_comment
public
Creates a new comment entry in the database associated with a specific post and user. This method handles the persistence of comment content and metadata, ensuring data integrity and proper attribution within the application.
def create_comment(db: Session, comment_create: CommentCreate, post_id: int, user_id: int) -> Comment:
-
db(Session)The SQLAlchemy database session object used for interacting with the database. This session manages transactions and persistence operations for the new comment record effectively and reliably.
-
comment_create(CommentCreate)A Pydantic schema object containing the data required to create a new comment. This includes the comment's content and optionally its parent comment ID for threaded discussions.
-
post_id(int)The unique identifier of the post to which this new comment will be attached. This integer ensures the comment is correctly linked to its parent content within the system.
-
user_id(int)The unique identifier of the user who is authoring this comment. This integer establishes the ownership and authorship of the comment for tracking and display purposes.
db_comment(Comment)The newly created
Commentobject, retrieved from the database after successful persistence. This object includes all database-generated fields likeidand timestamps, representing the final stored record.
ExceptionRaised for any unexpected errors occurring during the comment creation process. This catch-all ensures that database operations are rolled back and the error is propagated for higher-level handling.
- Begin a
tryblock to handle potential database errors during comment creation. - Instantiate a new
Commentobject, populating its fields fromcomment_createdata,post_id, anduser_id. - Add the newly created
db_commentobject to the SQLAlchemy database session for tracking. - Commit the transaction to persist the
db_commentrecord into the database permanently. - Refresh the
db_commentobject to load any database-generated attributes, such as its uniqueid. - Log an informational message indicating successful comment creation on the specified post.
- Return the refreshed
db_commentobject, representing the successfully stored comment. - If any
Exceptionoccurs during thetryblock:- Execute the
exceptblock to handle the error. - Rollback the database session to discard any uncommitted changes and maintain data consistency.
- Log an error message detailing the failure to create the comment, including the exception details.
- Re-raise the caught
Exceptionto propagate the error to the calling function for further handling.
- Execute the
Usage Tips
-
Always ensure
post_idanduser_idcorrespond to existing records before calling this method. Pre-validation prevents foreign key constraint violations and ensures data integrity within the application. -
Utilize the
CommentCreatePydantic model for consistent data validation and serialization. This approach standardizes input, reducing errors and improving maintainability across your API endpoints effectively.
Additional Notes
-
The
parent_comment_idfield allows for nested or threaded comment structures, enabling rich discussion features. Ensure your frontend correctly passes this optional ID for replies to specific comments. -
This method includes robust error handling with a database rollback, preventing partial data writes upon failure. This ensures that the database remains in a consistent state even during unexpected issues.
delete_comment
public
This method logically deletes a comment by setting its `deleted_at` timestamp. It ensures data integrity by marking records as inactive rather than physically removing them. This approach supports audit trails and potential recovery needs effectively.
def delete_comment(db: Session, comment_id: int, user_id: int) -> bool:
-
db(Session)Provides the database session for executing queries and managing transactions. It is essential for interacting with the persistence layer to retrieve and update comment records.
-
comment_id(int)Unique identifier for the comment to be deleted. This integer value precisely targets the specific comment record within the database for modification.
-
user_id(int)Identifier of the user performing the deletion. This integer value is recorded in the
updated_byfield, maintaining an audit trail of who initiated the comment's logical deletion.
deletion_status(bool)Indicates whether the comment was successfully marked as deleted. Returns
Trueupon successful logical deletion andFalseif the comment was not found, guiding subsequent application logic.
ExceptionRaised if any unexpected error occurs during the comment deletion process. This generic exception ensures that all unhandled issues are caught, logged, and re-propagated for higher-level error management.
- Begin a
tryblock to handle potential exceptions during the comment deletion process. - Retrieve the comment from the database using
CommentService.get_comment_by_idwith the provideddbsession andcomment_id. - Check if the
db_commentobject returned from the service call isNone(meaning the comment was not found):- If
db_commentisNone:- Return
Falseto indicate that the deletion was unsuccessful because the comment does not exist.
- Return
- If
- If
db_commentis found (notNone):- Set the
deleted_atattribute ofdb_commentto the current UTC datetime usingdatetime.utcnow(). - Set the
updated_byattribute ofdb_commentto the provideduser_id. - Commit the transaction to the database using
db.commit(), persisting the changes. - Log an informational message indicating the successful logical deletion of the comment, including its
comment_id. - Return
Trueto signify that the comment was successfully marked as deleted.
- Set the
- If any
Exceptionoccurs during thetryblock:- Enter the
exceptblock, catching the genericException. - Rollback the database transaction using
db.rollback()to undo any uncommitted changes. - Log an error message detailing the failure to delete the comment, including the exception string.
- Re-raise the caught
Exceptionto propagate the error further up the call stack for centralized handling.
- Enter the
Usage Tips
-
Always verify user permissions before calling
delete_commentto ensure only authorised users can logically remove comments. Implement robust access control checks at the API layer. -
Consider implementing a soft delete mechanism for all user-generated content, like this method. This preserves data for auditing, recovery, and compliance, avoiding permanent data loss effectively.
Additional Notes
-
This method performs a soft delete, marking the comment as deleted rather than physically removing it. This design choice is crucial for maintaining historical data and supporting potential undelete functionalities.
-
The
datetime.utcnow()function is used fordeleted_atto ensure timezone-agnostic timestamps. This consistency is vital for applications operating across different geographical regions and timezones.
get_comment_by_id
public
Retrieves a single `Comment` record from the database using its unique identifier. This method ensures only active, non-deleted comments are fetched, providing a reliable way to access specific comment data for display or further processing.
def get_comment_by_id(db: Session, comment_id: int) -> Comment:
-
db(Session)The SQLAlchemy database session object used to interact with the database. This session facilitates executing queries and managing transactions, ensuring data consistency and proper resource handling for all database operations.
-
comment_id(int)The unique integer identifier for the
Commentrecord to be retrieved. Thisidis crucial for precisely locating the desired comment within the database, ensuring accurate and efficient data fetching.
comment(Comment)Returns the
Commentobject matching the providedcomment_idif found and not deleted. If no such comment exists or it is marked as deleted,Noneis returned, indicating absence of an active record.
- Initiate a database query on the
Commentmodel using the provideddbsession. - Apply a filter condition to select comments where
Comment.idmatches thecomment_idparameter. - Add another filter condition using
and_to ensureComment.deleted_atisNone, meaning the comment is not soft-deleted. - Execute the query and retrieve the first matching
Commentobject found. - Return the retrieved
Commentobject, orNoneif no matching, non-deleted comment is found.
Usage Tips
-
Always check the return value for
Noneafter callingget_comment_by_id()to handle cases where the comment does not exist or is soft-deleted. This preventsAttributeErrorwhen accessing properties. -
Utilize this method when you need to fetch a specific comment for detailed viewing or modification. Avoid using it in loops for fetching multiple comments; prefer bulk query methods for performance optimization.
Additional Notes
-
This method implicitly handles soft-deletion by filtering out comments where
deleted_atis notNone. This ensures that only logically active comments are ever returned, maintaining data integrity and application logic. -
The
@staticmethoddecorator indicates this method does not require an instance of the class to be called. It operates purely on its input parameters, making it a utility function for database access.
get_post_comments
public
This method retrieves comments associated with a specific post from the database. It allows pagination and filtering for approved comments, providing a structured list of relevant feedback. This function is crucial for displaying user engagement on individual blog posts.
def get_post_comments(db: Session, post_id: int, skip: int = 0, limit: int = 20, approved_only: bool = True):
-
db(Session)The SQLAlchemy database session object used for executing queries. Provides the necessary interface to interact with the underlying database, ensuring transactional integrity and efficient data retrieval operations.
-
post_id(int)The unique identifier of the post for which comments are to be retrieved. This integer value is essential for filtering comments specific to a particular blog entry.
-
skip(int)The number of comments to skip from the beginning of the result set. Useful for pagination, allowing clients to request subsequent pages of comments efficiently. Defaults to zero for initial retrieval.
-
limit(int)The maximum number of comments to return in the result set. This integer value controls the size of each page in a paginated response. Defaults to twenty for manageable data chunks.
-
approved_only(bool)A boolean flag indicating whether to retrieve only approved comments. When
True, only comments marked as approved will be returned, ensuring content moderation. Defaults toTruefor public display.
comments_list(List[Comment])A list of
Commentobjects matching the specified criteria. Each object represents a comment with its associated data, ordered bydate_addedin descending order for chronological display.
- Initialize a database query targeting the
Commentmodel. - Filter the query to include comments where
Comment.post_idmatches the providedpost_idandComment.deleted_atisNone. - Check if the
approved_onlyparameter isTrue:- If
approved_onlyisTrue, further filter the query to include only comments whereComment.is_approvedisTrue.
- If
- Order the filtered query results by
Comment.date_addedin descending order. - Apply an offset to the query results using the
skipparameter. - Apply a limit to the query results using the
limitparameter. - Execute the query and retrieve all matching
Commentobjects as a list.
Usage Tips
-
To efficiently paginate comments, adjust
skipandlimitparameters based on the current page number. For example,skip = (page_number - 1) * limitensures correct sequential retrieval of comment pages. -
When displaying comments publicly, always set
approved_onlytoTrueto prevent unmoderated content from appearing. This maintains content quality and user experience on the platform.
Additional Notes
-
This method assumes
Comment.deleted_atbeingNonesignifies an active comment. If soft deletion logic changes, this query filter must be updated accordingly to reflect the new status. -
The
Commentobjects returned include all fields, potentially exposing sensitive data if not carefully handled by the calling context. Ensure proper serialization and access control for display.
update_comment
public
Updates an existing comment's content and metadata in the database. This method ensures data integrity by rolling back changes upon error. It is crucial for maintaining dynamic user-generated content within the application.
def update_comment(db: Session, comment_id: int, comment_update: CommentUpdate, user_id: int) -> Comment:
-
db(Session)The SQLAlchemy database session object used for database operations. This session manages transactions and persistence for the comment update. It ensures atomicity and consistency of data changes.
-
comment_id(int)The unique identifier of the comment to be updated. This
intvalue precisely targets the specific comment record in the database for modification. It ensures the correct comment is located. -
comment_update(CommentUpdate)An object containing the updated content for the comment. This
CommentUpdateschema provides the newcontentstring. It encapsulates the data necessary for modifying the existing comment record. -
user_id(int)The unique identifier of the user performing the update. This
intvalue is recorded asupdated_byto track who last modified the comment. It provides an audit trail for content changes.
-
db_comment(Comment)The updated
Commentobject retrieved from the database after successful modification. This object reflects all changes, including new content and updated timestamps. It confirms the operation's success. -
No Return Value
Returned if no comment is found matching the provided
comment_id. This indicates that the target comment does not exist in the database. Callers should handle this explicit absence of a comment.
ExceptionRaised for any unexpected errors occurring during the comment update process. This generic exception indicates a failure in database operations or other unforeseen issues. The transaction is rolled back.
- Begin a
tryblock to handle potential exceptions during the update process. - Retrieve the existing comment from the database using
CommentService.get_comment_by_idwithdbsession andcomment_id. - Check if
db_commentwas not found (i.e., isNone):- If
db_commentisNone, returnNoneimmediately, indicating no comment exists.
- If
- If
db_commentexists, update its attributes:- Set
db_comment.contenttocomment_update.content. - Set
db_comment.updated_byto theuser_idof the updater. - Set
db_comment.date_updatedto the current UTC time usingdatetime.utcnow().
- Set
- Commit the changes to the database using
db.commit()to persist the updated comment. - Refresh the
db_commentobject from the database usingdb.refresh()to ensure it reflects the latest state. - Log an informational message indicating the successful update of the comment with its
comment_id. - Return the updated
db_commentobject. - Begin an
except Exception as eblock to catch any exceptions that occurred during thetryblock:- Roll back the database transaction using
db.rollback()to undo any partial changes. - Log an error message detailing the exception
ethat occurred during the update. - Re-raise the caught
Exceptionto propagate the error to the calling function.
- Roll back the database transaction using
Usage Tips
-
Always verify the return value of
update_commentto handle cases where thecomment_iddoes not exist. ReturningNonerequires explicit handling to prevent downstream errors in your application logic. -
Ensure
comment_updatecontains only valid and sanitized data before calling this method. Pre-validation prevents database errors and maintains data integrity, enhancing the overall robustness of the system.
Additional Notes
-
The use of
datetime.utcnow()fordate_updatedensures consistency across different time zones. This is a best practice for timestamping database records, simplifying global data management and comparisons. -
This method relies on
CommentService.get_comment_by_idfor initial retrieval. Performance considerations forget_comment_by_iddirectly impact this update method's efficiency, especially under high load conditions.
FAQs
Why are multiple service classes grouped within a single file?
This design choice centralizes related business logic for content management entities, simplifying initial development and dependency management. It allows for quick access to all core services from one module.
How does the system handle deleted entities?
The system employs a soft deletion strategy by setting a
deleted_attimestamp. This preserves historical data and relationships, allowing for potential recovery while logically removing entities from active queries.
What is the purpose of passing user_id to creation and update methods?
Passing
user_idensures proper auditing and attribution for all entity modifications. It records which user performed an action, enhancing accountability and traceability within the system.
Why are Pydantic schemas used for data input and output?
Pydantic schemas provide robust data validation and serialization. They ensure that incoming data conforms to expected structures and types, and outgoing data is consistently formatted, improving API reliability and developer experience.
Insights
| Metric | Score | Level |
|---|---|---|
| Complexity | 1.00 | Very Complex |
| Security | 1.00 | Secure |
| Performance | 0.60 | Acceptable Performance |
Complexity
Medium - God File with Multiple Service Classes
Grouping all service classes (User, Post, Comment, Category, Tag) into a single file increases its size and reduces modularity. Splitting into separate files would improve maintainability.
Low - Repetitive Error Handling Logic
The
try...except...db.rollback()pattern is repeated across many methods. A decorator or context manager could centralize this logic, reducing boilerplate and improving consistency.
Performance
Medium - Missing Caching for Frequently Accessed Data
The service lacks caching mechanisms for frequently accessed data like categories or popular posts. This could lead to repeated database queries, impacting response times and database load.
Medium - Potential N+1 Query Issues
When fetching lists of posts or comments with related entities (e.g., author, tags), N+1 queries might occur. Eager loading or
selectinloadshould be considered for optimization.
Medium - Inefficient Search Query for Large Datasets
The
search_postsmethod usesilikefor full-text search, which can be inefficient on large datasets without proper database indexing or a dedicated search solution.