-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Books, Authors, marshmallow schemas, and others (#1)
* ci: rename pipeline script * feat: ✨ implement get and create methods for books and authors post and get methods implemented for both resources, pydantic removed, mashmallow schemas implemented, request validator decorator created * fix: adjust unit tests
- Loading branch information
1 parent
0668317
commit 5a4b5e9
Showing
23 changed files
with
363 additions
and
201 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
.github/workflows/backend.yml → .github/workflows/backend-linter.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from flask import g, jsonify, request | ||
from marshmallow import ValidationError | ||
from werkzeug.exceptions import BadRequest | ||
|
||
|
||
def validate_schema(schema): | ||
def decorator(f): | ||
def wrapper(*args, **kwargs): | ||
json_data = request.get_json() | ||
if not json_data: | ||
return jsonify({"message": "No input data provided"}), 400 | ||
|
||
try: | ||
data = schema.load(json_data) | ||
g.validated_data = data | ||
except ValidationError as err: | ||
raise BadRequest(err.messages) from err | ||
return f(*args, **kwargs) | ||
|
||
wrapper.__name__ = f.__name__ | ||
return wrapper | ||
|
||
return decorator |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from marshmallow import Schema, fields, validate | ||
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field | ||
from marshmallow_sqlalchemy.fields import Nested | ||
|
||
from backend.app.models.author_model import AuthorModel | ||
|
||
|
||
class AuthorCreateSchema(Schema): | ||
name = fields.Str(required=True, validate=validate.Length(min=1, max=300)) | ||
email = fields.Email(validate=validate.Length(min=1, max=320)) | ||
nationality = fields.Str(required=True, validate=validate.Length(min=1, max=100)) | ||
birthDate = fields.Date() | ||
|
||
|
||
class AuthorSchema(SQLAlchemySchema): | ||
class Meta: | ||
model = AuthorModel | ||
include_relationships = True | ||
load_instance = True | ||
|
||
id = auto_field() | ||
name = auto_field() | ||
email = auto_field() | ||
nationality = auto_field() | ||
birthDate = auto_field() | ||
books = Nested("BookSchema", many=True, exclude=["authors"]) | ||
|
||
|
||
author_schema = AuthorSchema() | ||
authors_schema = AuthorSchema(many=True) | ||
|
||
author_create_schema = AuthorCreateSchema() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from marshmallow import Schema, fields, validate, validates | ||
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field | ||
from marshmallow_sqlalchemy.fields import Nested | ||
from werkzeug.exceptions import BadRequest | ||
|
||
from backend.app.models.book_model import BookModel | ||
from backend.app.schemas.author_schema import AuthorSchema, AuthorCreateSchema | ||
|
||
|
||
class AuthorFindOrCreateSchema(Schema): | ||
existentAuthorId = fields.Int() | ||
authorCreationPayload = fields.Nested(AuthorCreateSchema) | ||
|
||
|
||
class BookCreateSchema(Schema): | ||
title = fields.Str(required=True, validate=validate.Length(max=300)) | ||
authors = fields.Nested(AuthorFindOrCreateSchema, many=True) | ||
pages = fields.Int(required=True) | ||
|
||
@validates("authors") | ||
def items_must_not_be_empty(self, value): | ||
if not value: | ||
raise BadRequest("List of authors cannot be empty") | ||
|
||
for item in value: | ||
if not item.get("existentAuthorId") and not item.get("authorCreationPayload"): | ||
raise BadRequest("existentAuthorId or authorCreationPayload is required") | ||
|
||
|
||
class BookSchema(SQLAlchemySchema): | ||
class Meta: | ||
model = BookModel | ||
include_relationships = True | ||
load_instance = True | ||
|
||
id = auto_field() | ||
title = auto_field() | ||
authors = Nested(AuthorSchema, many=True, exclude=["books"]) | ||
pages = auto_field() | ||
|
||
|
||
book_schema = BookSchema() | ||
books_schema = BookSchema(many=True) | ||
|
||
book_create_schema = BookCreateSchema() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,54 @@ | ||
from typing import Dict, List | ||
from werkzeug.exceptions import BadRequest | ||
|
||
from backend.app.models.model import db | ||
from backend.app.models.author_model import AuthorModel | ||
from backend.app.models.book_model import BookModel | ||
from backend.app.dtos.book_dto import BookCreateDto, BookResponseDto | ||
from backend.app.dtos.author_dto import AuthorCreateDto, AuthorResponseDto | ||
from backend.app.models.model import db | ||
from backend.app.schemas.author_schema import AuthorSchema, author_schema, authors_schema | ||
from backend.app.schemas.book_schema import AuthorFindOrCreateSchema, books_schema, book_schema | ||
|
||
|
||
class ManagementService: | ||
def get_authors(self): | ||
authors = AuthorModel.query.all() | ||
|
||
return [AuthorResponseDto(**author.__dict__).__dict__ for author in authors] | ||
return authors_schema.dump(authors) | ||
|
||
def get_books(self): | ||
books = BookModel.query.all() | ||
|
||
return [BookResponseDto(**book.__dict__).__dict__ for book in books] | ||
return books_schema.dump(books) | ||
|
||
def create_author(self, author: AuthorCreateDto): | ||
data = author.__dict__ | ||
def create_author(self, author_data: Dict) -> AuthorSchema: | ||
with db.session.begin(): | ||
author = AuthorModel(**data) | ||
author = AuthorModel(**author_data) | ||
db.session.add(author) | ||
db.session.commit() | ||
return author_schema.dump(author) | ||
|
||
def create_book(self, book: BookCreateDto): | ||
print(book) | ||
def create_book(self, book_dto: Dict): | ||
with db.session.begin(): | ||
books = BookModel.query.all() | ||
authors = self._get_authors_for_book_creation(book_dto.get("authors")) | ||
del book_dto["authors"] | ||
book = BookModel(**book_dto) | ||
book.authors = authors | ||
db.session.add(book) | ||
|
||
return book_schema.dump(book) | ||
|
||
def _get_authors_for_book_creation(self, authors_dto: List[AuthorFindOrCreateSchema]): | ||
authors = [self._find_or_create_author(author) for author in authors_dto] | ||
|
||
return authors | ||
|
||
def _find_or_create_author( | ||
self, author_dto: Dict[str, AuthorFindOrCreateSchema] | ||
) -> AuthorModel: | ||
existent_id = author_dto.get("existentAuthorId") | ||
if existent_id is None: | ||
return AuthorModel(**author_dto.get("authorCreationPayload")) | ||
|
||
author = AuthorModel.query.get(existent_id) | ||
if author is None: | ||
raise BadRequest(f"author of id {existent_id} was not found") | ||
|
||
return books | ||
return author |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
backend/migrations/versions/17eb199d7416_initial_commit.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""Initial commit | ||
Revision ID: 17eb199d7416 | ||
Revises: | ||
Create Date: 2024-05-16 16:58:03.648098 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = '17eb199d7416' | ||
down_revision = None | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table('Author', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('name', sa.String(length=300), nullable=False), | ||
sa.Column('email', sa.String(length=320), nullable=True), | ||
sa.Column('nationality', sa.String(length=100), nullable=False), | ||
sa.Column('birthDate', sa.Date(), nullable=False), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_table('Book', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('title', sa.String(length=300), nullable=False), | ||
sa.Column('pages', sa.Integer(), nullable=False), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_table('BookAuthor', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('bookId', sa.Integer(), nullable=True), | ||
sa.Column('authorId', sa.Integer(), nullable=True), | ||
sa.ForeignKeyConstraint(['authorId'], ['Author.id'], ), | ||
sa.ForeignKeyConstraint(['bookId'], ['Book.id'], ), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_table('BookAuthor') | ||
op.drop_table('Book') | ||
op.drop_table('Author') | ||
# ### end Alembic commands ### |
Oops, something went wrong.