From 5ff95a52c946311c494975f0b58ebebb9ed8dcce Mon Sep 17 00:00:00 2001 From: EvgeniyD Date: Sat, 29 Apr 2023 21:43:30 +0500 Subject: [PATCH 1/3] add print fact and file number of pages --- .../686a37a323be_add_file_number_of_pages.py | 28 ++++++++++++ .../versions/f6fb6304fb74_add_print_fact.py | 43 +++++++++++++++++++ print_service/models/__init__.py | 15 +++++++ print_service/routes/file.py | 5 ++- print_service/utils/__init__.py | 12 ++++-- tests/test_routes/test_file.py | 8 ++-- 6 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 migrations/versions/686a37a323be_add_file_number_of_pages.py create mode 100644 migrations/versions/f6fb6304fb74_add_print_fact.py diff --git a/migrations/versions/686a37a323be_add_file_number_of_pages.py b/migrations/versions/686a37a323be_add_file_number_of_pages.py new file mode 100644 index 0000000..bd52b7b --- /dev/null +++ b/migrations/versions/686a37a323be_add_file_number_of_pages.py @@ -0,0 +1,28 @@ +"""add_file_number_of_pages + +Revision ID: 686a37a323be +Revises: 692fe4f50da7 +Create Date: 2023-04-29 19:38:02.676614 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '686a37a323be' +down_revision = '692fe4f50da7' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('file', sa.Column('number_of_pages', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('file', 'number_of_pages') + # ### end Alembic commands ### diff --git a/migrations/versions/f6fb6304fb74_add_print_fact.py b/migrations/versions/f6fb6304fb74_add_print_fact.py new file mode 100644 index 0000000..466dd98 --- /dev/null +++ b/migrations/versions/f6fb6304fb74_add_print_fact.py @@ -0,0 +1,43 @@ +"""add_print_fact + +Revision ID: f6fb6304fb74 +Revises: 686a37a323be +Create Date: 2023-04-29 21:36:34.034457 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = 'f6fb6304fb74' +down_revision = '686a37a323be' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'print_fact', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('file_id', sa.Integer(), nullable=False), + sa.Column('owner_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ['file_id'], + ['file.id'], + ), + sa.ForeignKeyConstraint( + ['owner_id'], + ['union_member.id'], + ), + sa.PrimaryKeyConstraint('id'), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('print_fact') + # ### end Alembic commands ### diff --git a/print_service/models/__init__.py b/print_service/models/__init__.py index ff6cb45..7df12c5 100644 --- a/print_service/models/__init__.py +++ b/print_service/models/__init__.py @@ -23,6 +23,7 @@ class UnionMember(Model): student_number: Mapped[str] = mapped_column(String, nullable=True) files: Mapped[list[File]] = relationship('File', back_populates='owner') + print_facts: Mapped[list[PrintFact]] = relationship('PrintFact', back_populates='owner') class File(Model): @@ -39,5 +40,19 @@ class File(Model): updated_at: Mapped[datetime] = Column( DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow ) + number_of_pages: Mapped[int] = Column(Integer) owner: Mapped[UnionMember] = relationship('UnionMember', back_populates='files') + print_facts: Mapped[list[PrintFact]] = relationship('PrintFact', back_populates='file') + + +class PrintFact(Model): + __tablename__ = 'print_fact' + + id: Mapped[int] = Column(Integer, primary_key=True) + file_id: Mapped[int] = Column(Integer, ForeignKey('file.id'), nullable=False) + owner_id: Mapped[int] = Column(Integer, ForeignKey('union_member.id'), nullable=False) + created_at: Mapped[datetime] = Column(DateTime, nullable=False, default=datetime.utcnow) + + owner: Mapped[UnionMember] = relationship('UnionMember', back_populates='print_facts') + file: Mapped[File] = relationship('File', back_populates='print_facts') diff --git a/print_service/routes/file.py b/print_service/routes/file.py index 507031f..60b0126 100644 --- a/print_service/routes/file.py +++ b/print_service/routes/file.py @@ -173,7 +173,10 @@ async def upload_file( if len(memory_file) > settings.MAX_SIZE: raise HTTPException(413, f'Content too large, {settings.MAX_SIZE} bytes allowed') await saved_file.write(memory_file) - if not check_pdf_ok(memory_file): + pdf_ok, number_of_pages = check_pdf_ok(memory_file) + file_model.number_of_pages = number_of_pages + db.session.commit() + if not pdf_ok: await aiofiles.os.remove(path) raise HTTPException(415, 'File corrupted') await file.close() diff --git a/print_service/utils/__init__.py b/print_service/utils/__init__.py index ee9ad3b..57192df 100644 --- a/print_service/utils/__init__.py +++ b/print_service/utils/__init__.py @@ -12,6 +12,7 @@ from print_service.models import File from print_service.models import File as FileModel +from print_service.models import PrintFact from print_service.settings import Settings, get_settings @@ -72,12 +73,15 @@ def get_file(dbsession, pin: str or list[str]): }, } ) + file_model = PrintFact(file_id=f.id, owner_id=f.owner_id) + dbsession.add(file_model) + dbsession.commit() return result -def check_pdf_ok(f: bytes): +def check_pdf_ok(f: bytes) -> bool | int: try: - PdfFileReader(io.BytesIO(f)) - return True + pdf_file = PdfFileReader(io.BytesIO(f)) + return True, pdf_file.getNumPages() except Exception: - return False + return False, 0 diff --git a/tests/test_routes/test_file.py b/tests/test_routes/test_file.py index f032fd8..1070cf6 100644 --- a/tests/test_routes/test_file.py +++ b/tests/test_routes/test_file.py @@ -91,8 +91,8 @@ def test_get_file_func_2_not_exists(dbsession, uploaded_file_os): def test_file_check(): - assert check_pdf_ok(open("tests/test_routes/test_files/broken.pdf", "rb").read()) is False - assert check_pdf_ok(open("tests/test_routes/test_files/correct.pdf", "rb").read()) is True + assert check_pdf_ok(open("tests/test_routes/test_files/broken.pdf", "rb").read())[0] is False + assert check_pdf_ok(open("tests/test_routes/test_files/correct.pdf", "rb").read())[0] is True def test_upload_and_print_correct_pdf(pin_pdf, client): @@ -130,9 +130,9 @@ def test_upload_and_print_encrypted_file(pin_pdf, client): fileName = 'tests/test_routes/test_files/encrypted.pdf' files = {'file': (f"{fileName}", open(f"{fileName}", 'rb'), "application/pdf")} res = client.post(f"{url}/{pin}", files=files) - assert res.status_code == status.HTTP_200_OK + assert res.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE res2 = client.get(f"{url}/{pin}") - assert res2.status_code == status.HTTP_200_OK + assert res2.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE def test_incorrect_filename(union_member_user, client, dbsession): From 51a2a89eef75ee8e99e3d0d432e6abcf460b68f2 Mon Sep 17 00:00:00 2001 From: EvgeniyD Date: Sun, 30 Apr 2023 16:01:40 +0500 Subject: [PATCH 2/3] add new model to tests --- tests/test_routes/conftest.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_routes/conftest.py b/tests/test_routes/conftest.py index b39a089..e9f36cd 100644 --- a/tests/test_routes/conftest.py +++ b/tests/test_routes/conftest.py @@ -3,7 +3,7 @@ import pytest -from print_service.models import File, UnionMember +from print_service.models import File, PrintFact, UnionMember @pytest.fixture(scope='function') @@ -19,6 +19,7 @@ def union_member_user(dbsession): yield union_member db_user = dbsession.query(UnionMember).filter(UnionMember.id == union_member['id']).one_or_none() assert db_user is not None + dbsession.query(PrintFact).filter(PrintFact.owner_id == union_member['id']).delete() dbsession.query(UnionMember).filter(UnionMember.id == union_member['id']).delete() dbsession.commit() @@ -34,8 +35,11 @@ def uploaded_file_db(dbsession, union_member_user, client): res = client.post('/file', json=body) db_file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() yield db_file - dbsession.query(File).filter(File.pin == res.json()['pin']).delete() - dbsession.commit() + file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() + if file is not None: + dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() + dbsession.query(File).filter(File.pin == res.json()['pin']).delete() + dbsession.commit() @pytest.fixture @@ -57,5 +61,8 @@ def pin_pdf(dbsession, union_member_user, client): res = client.post('/file', json=body) pin = res.json()['pin'] yield pin - dbsession.query(File).filter(File.pin == res.json()['pin']).delete() - dbsession.commit() + file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() + if file is not None: + dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() + dbsession.query(File).filter(File.pin == res.json()['pin']).delete() + dbsession.commit() From 9a0f59d7b01bb270119b8c3b5924cc84f8c59782 Mon Sep 17 00:00:00 2001 From: EvgeniyD Date: Tue, 2 May 2023 16:59:58 +0500 Subject: [PATCH 3/3] review --- .../versions/44494b133481_print_options.py | 4 ---- .../686a37a323be_add_file_number_of_pages.py | 4 ---- .../versions/692fe4f50da7_upper_surnames.py | 2 -- .../versions/f6fb6304fb74_add_print_fact.py | 4 ---- print_service/routes/file.py | 4 ++-- print_service/utils/__init__.py | 11 ++++++++++- tests/test_routes/conftest.py | 17 ++++++++--------- tests/test_routes/test_file.py | 10 +++------- 8 files changed, 23 insertions(+), 33 deletions(-) diff --git a/migrations/versions/44494b133481_print_options.py b/migrations/versions/44494b133481_print_options.py index adb6e84..b809a92 100644 --- a/migrations/versions/44494b133481_print_options.py +++ b/migrations/versions/44494b133481_print_options.py @@ -17,16 +17,12 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.add_column('file', sa.Column('option_pages', sa.String(), nullable=True)) op.add_column('file', sa.Column('option_copies', sa.Integer(), nullable=True)) op.add_column('file', sa.Column('option_two_sided', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.drop_column('file', 'option_two_sided') op.drop_column('file', 'option_copies') op.drop_column('file', 'option_pages') - # ### end Alembic commands ### diff --git a/migrations/versions/686a37a323be_add_file_number_of_pages.py b/migrations/versions/686a37a323be_add_file_number_of_pages.py index bd52b7b..9823dd8 100644 --- a/migrations/versions/686a37a323be_add_file_number_of_pages.py +++ b/migrations/versions/686a37a323be_add_file_number_of_pages.py @@ -17,12 +17,8 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.add_column('file', sa.Column('number_of_pages', sa.Integer(), nullable=True)) - # ### end Alembic commands ### def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.drop_column('file', 'number_of_pages') - # ### end Alembic commands ### diff --git a/migrations/versions/692fe4f50da7_upper_surnames.py b/migrations/versions/692fe4f50da7_upper_surnames.py index 1be0390..2b572ce 100644 --- a/migrations/versions/692fe4f50da7_upper_surnames.py +++ b/migrations/versions/692fe4f50da7_upper_surnames.py @@ -21,6 +21,4 @@ def upgrade(): def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### pass - # ### end Alembic commands ### diff --git a/migrations/versions/f6fb6304fb74_add_print_fact.py b/migrations/versions/f6fb6304fb74_add_print_fact.py index 466dd98..0becce1 100644 --- a/migrations/versions/f6fb6304fb74_add_print_fact.py +++ b/migrations/versions/f6fb6304fb74_add_print_fact.py @@ -17,7 +17,6 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.create_table( 'print_fact', sa.Column('id', sa.Integer(), nullable=False), @@ -34,10 +33,7 @@ def upgrade(): ), sa.PrimaryKeyConstraint('id'), ) - # ### end Alembic commands ### def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.drop_table('print_fact') - # ### end Alembic commands ### diff --git a/print_service/routes/file.py b/print_service/routes/file.py index 60b0126..7da7ae0 100644 --- a/print_service/routes/file.py +++ b/print_service/routes/file.py @@ -15,7 +15,7 @@ from print_service.models import UnionMember from print_service.schema import BaseModel from print_service.settings import Settings, get_settings -from print_service.utils import check_pdf_ok, generate_filename, generate_pin, get_file +from print_service.utils import checking_for_pdf, generate_filename, generate_pin, get_file logger = logging.getLogger(__name__) @@ -173,7 +173,7 @@ async def upload_file( if len(memory_file) > settings.MAX_SIZE: raise HTTPException(413, f'Content too large, {settings.MAX_SIZE} bytes allowed') await saved_file.write(memory_file) - pdf_ok, number_of_pages = check_pdf_ok(memory_file) + pdf_ok, number_of_pages = checking_for_pdf(memory_file) file_model.number_of_pages = number_of_pages db.session.commit() if not pdf_ok: diff --git a/print_service/utils/__init__.py b/print_service/utils/__init__.py index 57192df..9f32af1 100644 --- a/print_service/utils/__init__.py +++ b/print_service/utils/__init__.py @@ -79,7 +79,16 @@ def get_file(dbsession, pin: str or list[str]): return result -def check_pdf_ok(f: bytes) -> bool | int: +def checking_for_pdf(f: bytes) -> tuple[bool, int]: + """_summary_ + + Args: + f (bytes): file to check + + Returns: + tuple[bool, int]: The first argument returns whether the file is a valid pdf. + The second argument returns the number of pages in the pdf document (0- if the check failed) + """ try: pdf_file = PdfFileReader(io.BytesIO(f)) return True, pdf_file.getNumPages() diff --git a/tests/test_routes/conftest.py b/tests/test_routes/conftest.py index e9f36cd..8eb798b 100644 --- a/tests/test_routes/conftest.py +++ b/tests/test_routes/conftest.py @@ -1,5 +1,4 @@ import os -from unittest.mock import Mock import pytest @@ -36,10 +35,10 @@ def uploaded_file_db(dbsession, union_member_user, client): db_file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() yield db_file file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() - if file is not None: - dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() - dbsession.query(File).filter(File.pin == res.json()['pin']).delete() - dbsession.commit() + assert file is not None + dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() + dbsession.query(File).filter(File.pin == res.json()['pin']).delete() + dbsession.commit() @pytest.fixture @@ -62,7 +61,7 @@ def pin_pdf(dbsession, union_member_user, client): pin = res.json()['pin'] yield pin file = dbsession.query(File).filter(File.pin == res.json()['pin']).one_or_none() - if file is not None: - dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() - dbsession.query(File).filter(File.pin == res.json()['pin']).delete() - dbsession.commit() + assert file is not None + dbsession.query(PrintFact).filter(PrintFact.file_id == file.id).delete() + dbsession.query(File).filter(File.pin == res.json()['pin']).delete() + dbsession.commit() diff --git a/tests/test_routes/test_file.py b/tests/test_routes/test_file.py index 1070cf6..9a93245 100644 --- a/tests/test_routes/test_file.py +++ b/tests/test_routes/test_file.py @@ -1,8 +1,4 @@ -import asyncio -import datetime import json -import time -from concurrent.futures import ThreadPoolExecutor import pytest from fastapi import HTTPException @@ -10,7 +6,7 @@ from print_service.models import File from print_service.settings import get_settings -from print_service.utils import check_pdf_ok, generate_filename, get_file +from print_service.utils import checking_for_pdf, get_file url = '/file' @@ -91,8 +87,8 @@ def test_get_file_func_2_not_exists(dbsession, uploaded_file_os): def test_file_check(): - assert check_pdf_ok(open("tests/test_routes/test_files/broken.pdf", "rb").read())[0] is False - assert check_pdf_ok(open("tests/test_routes/test_files/correct.pdf", "rb").read())[0] is True + assert checking_for_pdf(open("tests/test_routes/test_files/broken.pdf", "rb").read()) == (False, 0) + assert checking_for_pdf(open("tests/test_routes/test_files/correct.pdf", "rb").read()) == (True, 2) def test_upload_and_print_correct_pdf(pin_pdf, client):